Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
subjective_billing.hpp
Go to the documentation of this file.
1#pragma once
2
10
11#include <boost/multi_index_container.hpp>
12#include <boost/multi_index/member.hpp>
13#include <boost/multi_index/hashed_index.hpp>
14#include <boost/multi_index/ordered_index.hpp>
15
16namespace sysio {
17
18namespace bmi = boost::multi_index;
22using chain::packed_transaction;
23namespace config = chain::config;
24
26private:
27
28 struct trx_cache_entry {
30 account_name account;
31 int64_t subjective_cpu_bill;
32 fc::time_point expiry;
33 };
34 struct by_id;
35 struct by_expiry;
36
37 using trx_cache_index = bmi::multi_index_container<
38 trx_cache_entry,
39 indexed_by<
40 bmi::hashed_unique<tag<by_id>, BOOST_MULTI_INDEX_MEMBER( trx_cache_entry, transaction_id_type, trx_id ) >,
41 ordered_non_unique<tag<by_expiry>, BOOST_MULTI_INDEX_MEMBER( trx_cache_entry, fc::time_point, expiry ) >
42 >
43 >;
44
46
47 struct subjective_billing_info {
48 uint64_t pending_cpu_us; // tracked cpu us for transactions that may still succeed in a block
49 decaying_accumulator expired_accumulator; // accumulator used to account for transactions that have expired
50
51 bool empty(uint32_t time_ordinal, uint32_t expired_accumulator_average_window) {
52 return pending_cpu_us == 0 && expired_accumulator.value_at(time_ordinal, expired_accumulator_average_window) == 0;
53 }
54 };
55
56 using account_subjective_bill_cache = std::map<account_name, subjective_billing_info>;
57 using block_subjective_bill_cache = std::map<account_name, uint64_t>;
58
59 bool _disabled = false;
60 trx_cache_index _trx_cache_index;
61 account_subjective_bill_cache _account_subjective_bill_cache;
62 block_subjective_bill_cache _block_subjective_bill_cache;
63 std::set<chain::account_name> _disabled_accounts;
64 uint32_t _expired_accumulator_average_window = config::account_cpu_usage_average_window_ms / subjective_time_interval_ms;
65
66private:
67 uint32_t time_ordinal_for( const fc::time_point& t ) const {
68 auto ordinal = t.time_since_epoch().count() / (1000U * (uint64_t)subjective_time_interval_ms);
69 SYS_ASSERT(ordinal <= std::numeric_limits<uint32_t>::max(), chain::tx_resource_exhaustion, "overflow of quantized time in subjective billing");
70 return ordinal;
71 }
72
73 void remove_subjective_billing( const trx_cache_entry& entry, uint32_t time_ordinal ) {
74 auto aitr = _account_subjective_bill_cache.find( entry.account );
75 if( aitr != _account_subjective_bill_cache.end() ) {
76 aitr->second.pending_cpu_us -= entry.subjective_cpu_bill;
77 SYS_ASSERT( aitr->second.pending_cpu_us >= 0, chain::tx_resource_exhaustion,
78 "Logic error in subjective account billing ${a}", ("a", entry.account) );
79 if( aitr->second.empty(time_ordinal, _expired_accumulator_average_window) ) _account_subjective_bill_cache.erase( aitr );
80 }
81 }
82
83 void transition_to_expired( const trx_cache_entry& entry, uint32_t time_ordinal ) {
84 auto aitr = _account_subjective_bill_cache.find( entry.account );
85 if( aitr != _account_subjective_bill_cache.end() ) {
86 aitr->second.pending_cpu_us -= entry.subjective_cpu_bill;
87 aitr->second.expired_accumulator.add(entry.subjective_cpu_bill, time_ordinal, _expired_accumulator_average_window);
88 }
89 }
90
91 void remove_subjective_billing( const block_state_ptr& bsp, uint32_t time_ordinal ) {
92 if( !_trx_cache_index.empty() ) {
93 for( const auto& receipt : bsp->block->transactions ) {
94 if( std::holds_alternative<packed_transaction>(receipt.trx) ) {
95 const auto& pt = std::get<packed_transaction>(receipt.trx);
96 remove_subjective_billing( pt.id(), time_ordinal );
97 }
98 }
99 }
100 }
101
102public: // public for tests
103 static constexpr uint32_t subjective_time_interval_ms = 5'000;
104
105 void remove_subjective_billing( const transaction_id_type& trx_id, uint32_t time_ordinal ) {
106 auto& idx = _trx_cache_index.get<by_id>();
107 auto itr = idx.find( trx_id );
108 if( itr != idx.end() ) {
109 remove_subjective_billing( *itr, time_ordinal );
110 idx.erase( itr );
111 }
112 }
113
114public:
115 void disable() { _disabled = true; }
116 void disable_account( chain::account_name a ) { _disabled_accounts.emplace( a ); }
117 bool is_account_disabled(const account_name& a ) const { return _disabled || _disabled_accounts.count( a ); }
118
120 void subjective_bill( const transaction_id_type& id, const fc::time_point& expire, const account_name& first_auth,
121 const fc::microseconds& elapsed, bool in_pending_block )
122 {
123 if( !_disabled && !_disabled_accounts.count( first_auth ) ) {
124 int64_t bill = std::max<int64_t>( 0, elapsed.count() );
125 auto p = _trx_cache_index.emplace(
126 trx_cache_entry{id,
127 first_auth,
128 bill,
129 expire} );
130 if( p.second ) {
131 _account_subjective_bill_cache[first_auth].pending_cpu_us += bill;
132 if( in_pending_block ) {
133 _block_subjective_bill_cache[first_auth] += bill;
134 }
135 }
136 }
137 }
138
139 void subjective_bill_failure( const account_name& first_auth, const fc::microseconds& elapsed, const fc::time_point& now )
140 {
141 if( !_disabled && !_disabled_accounts.count( first_auth ) ) {
142 int64_t bill = std::max<int64_t>( 0, elapsed.count() );
143 const auto time_ordinal = time_ordinal_for(now);
144 _account_subjective_bill_cache[first_auth].expired_accumulator.add(bill, time_ordinal, _expired_accumulator_average_window);
145 }
146 }
147
148 int64_t get_subjective_bill( const account_name& first_auth, const fc::time_point& now ) const {
149 if( _disabled || _disabled_accounts.count( first_auth ) ) return 0;
150 const auto time_ordinal = time_ordinal_for(now);
151 const subjective_billing_info* sub_bill_info = nullptr;
152 auto aitr = _account_subjective_bill_cache.find( first_auth );
153 if( aitr != _account_subjective_bill_cache.end() ) {
154 sub_bill_info = &aitr->second;
155 }
156 uint64_t in_block_pending_cpu_us = 0;
157 auto bitr = _block_subjective_bill_cache.find( first_auth );
158 if( bitr != _block_subjective_bill_cache.end() ) {
159 in_block_pending_cpu_us = bitr->second;
160 }
161
162 if (sub_bill_info) {
163 SYS_ASSERT(sub_bill_info->pending_cpu_us >= in_block_pending_cpu_us, chain::tx_resource_exhaustion, "Logic error subjective billing ${a}", ("a", first_auth) );
164 int64_t sub_bill = sub_bill_info->pending_cpu_us - in_block_pending_cpu_us + sub_bill_info->expired_accumulator.value_at(time_ordinal, _expired_accumulator_average_window );
165 return sub_bill;
166 } else {
167 return 0;
168 }
169 }
170
171 void abort_block() {
172 _block_subjective_bill_cache.clear();
173 }
174
175 void on_block( fc::logger& log, const block_state_ptr& bsp, const fc::time_point& now ) {
176 if( bsp == nullptr || _disabled ) return;
177 const auto time_ordinal = time_ordinal_for(now);
178 const auto orig_count = _account_subjective_bill_cache.size();
179 remove_subjective_billing( bsp, time_ordinal );
180 fc_dlog( log, "Subjective billed accounts ${n} removed ${r}", ("n", orig_count)("r", orig_count - _account_subjective_bill_cache.size()) );
181 }
182
183 template <typename Yield>
184 bool remove_expired( fc::logger& log, const fc::time_point& pending_block_time, const fc::time_point& now, Yield&& yield ) {
185 bool exhausted = false;
186 auto& idx = _trx_cache_index.get<by_expiry>();
187 if( !idx.empty() ) {
188 const auto time_ordinal = time_ordinal_for(now);
189 const auto orig_count = _trx_cache_index.size();
190 uint32_t num_expired = 0;
191
192 while( !idx.empty() ) {
193 if( yield() ) {
194 exhausted = true;
195 break;
196 }
197 auto b = idx.begin();
198 if( b->expiry > pending_block_time ) break;
199 transition_to_expired( *b, time_ordinal );
200 idx.erase( b );
201 num_expired++;
202 }
203
204 fc_dlog( log, "Processed ${n} subjective billed transactions, Expired ${expired}",
205 ("n", orig_count)( "expired", num_expired ) );
206 }
207 return !exhausted;
208 }
209
211 return _expired_accumulator_average_window;
212 }
213
214 void set_expired_accumulator_average_window( fc::microseconds subjective_account_decay_time ) {
215 _expired_accumulator_average_window =
216 subjective_account_decay_time.count() / 1000 / subjective_time_interval_ms;
217 }
218};
219
220} //sysio
const mie::Vuint & p
Definition bn.cpp:27
#define SYS_ASSERT(expr, exc_type, FORMAT,...)
Definition exceptions.hpp:7
constexpr int64_t count() const
Definition time.hpp:26
constexpr const microseconds & time_since_epoch() const
Definition time.hpp:52
void set_expired_accumulator_average_window(fc::microseconds subjective_account_decay_time)
int64_t get_subjective_bill(const account_name &first_auth, const fc::time_point &now) const
void subjective_bill(const transaction_id_type &id, const fc::time_point &expire, const account_name &first_auth, const fc::microseconds &elapsed, bool in_pending_block)
void subjective_bill_failure(const account_name &first_auth, const fc::microseconds &elapsed, const fc::time_point &now)
void disable_account(chain::account_name a)
bool remove_expired(fc::logger &log, const fc::time_point &pending_block_time, const fc::time_point &now, Yield &&yield)
bool is_account_disabled(const account_name &a) const
uint32_t get_expired_accumulator_average_window() const
static constexpr uint32_t subjective_time_interval_ms
void remove_subjective_billing(const transaction_id_type &trx_id, uint32_t time_ordinal)
void on_block(fc::logger &log, const block_state_ptr &bsp, const fc::time_point &now)
uint64_t id
Definition code_cache.cpp:0
#define fc_dlog(LOGGER, FORMAT,...)
Definition logger.hpp:77
checksum_type transaction_id_type
Definition types.hpp:236
std::shared_ptr< block_state > block_state_ptr
name account_name
Definition types.hpp:120
const GenericPointer< typename T::ValueType > T2 T::AllocatorType & a
Definition pointer.h:1181
signed __int64 int64_t
Definition stdint.h:135
unsigned int uint32_t
Definition stdint.h:126
unsigned __int64 uint64_t
Definition stdint.h:136
Immutable except for fc::from_variant.
Definition name.hpp:43
uint64_t value_at(uint32_t ordinal, uint32_t window_size) const