-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathethscriptions_controller.rb
More file actions
351 lines (276 loc) · 11.4 KB
/
Copy pathethscriptions_controller.rb
File metadata and controls
351 lines (276 loc) · 11.4 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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
class EthscriptionsController < ApplicationController
cache_actions_on_block only: [:index, :show, :newer_ethscriptions]
def index
if params[:owned_by_address].present?
params[:current_owner] = params[:owned_by_address]
end
scope = filter_by_params(Ethscription.all,
:current_owner,
:creator,
:initial_owner,
:previous_owner,
:mimetype,
:media_type,
:mime_subtype,
:content_sha,
:transaction_hash,
:block_number,
:block_timestamp,
:block_blockhash,
:ethscription_number,
:attachment_sha,
:attachment_content_type
)
if params[:after_block].present?
scope = scope.where('block_number > ?', params[:after_block].to_i)
end
if params[:before_block].present?
scope = scope.where('block_number < ?', params[:before_block].to_i)
end
scope = scope.where.not(attachment_sha: nil) if params[:attachment_present] == "true"
scope = scope.where(attachment_sha: nil) if params[:attachment_present] == "false"
include_latest_transfer = params[:include_latest_transfer].present?
if include_latest_transfer
scope = scope.includes(:ethscription_transfers)
end
token_tick = parse_param_array(params[:token_tick]).first
token_protocol = parse_param_array(params[:token_protocol]).first
transferred_in_tx = parse_param_array(params[:transferred_in_tx])
if token_tick && token_protocol
scope = scope.with_token_tick_and_protocol(token_tick, token_protocol)
end
if transferred_in_tx.present?
sub_query = EthscriptionTransfer.where(transaction_hash: transferred_in_tx).select(:ethscription_transaction_hash)
scope = scope.where(transaction_hash: sub_query)
end
transaction_hash_only = params[:transaction_hash_only].present? && !include_latest_transfer
if transaction_hash_only
scope = scope.select(:id, :transaction_hash)
end
results_limit = if transaction_hash_only
1000
elsif include_latest_transfer
50
else
100
end
results, pagination_response = paginate(
scope,
results_limit: results_limit
)
results = results.map do |ethscription|
ethscription.as_json(include_latest_transfer: include_latest_transfer)
end
render json: {
result: numbers_to_strings(results),
pagination: pagination_response
}
end
def show
scope = Ethscription.all.includes(:ethscription_transfers)
id_or_hash = params[:id].to_s.downcase
scope = id_or_hash.match?(/\A0x[0-9a-f]{64}\z/) ?
scope.where(transaction_hash: id_or_hash) :
scope.where(ethscription_number: id_or_hash)
ethscription = Rails.cache.fetch(["ethscription-api-show", scope]) do
scope.first
end
if !ethscription
render json: { error: "Not found" }, status: 404
return
end
json = numbers_to_strings(ethscription.as_json(include_transfers: true))
render json: {
result: json
}
end
def data
scope = Ethscription.all
id_or_hash = params[:id].to_s.downcase
scope = id_or_hash.match?(/\A0x[0-9a-f]{64}\z/) ?
scope.where(transaction_hash: id_or_hash) :
scope.where(ethscription_number: id_or_hash)
blockhash, block_number = scope.pick(:block_blockhash, :block_number)
unless blockhash.present?
# Ensure CORS headers are set even for 404 responses
if request.headers['Origin']
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization'
response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin'
end
head 404
return
end
response.headers.delete('X-Frame-Options')
# Ensure CORS headers are set for cross-origin requests
if request.headers['Origin']
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization'
response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin'
end
set_cache_control_headers(
max_age: 6,
s_max_age: 1.minute,
etag: blockhash,
extend_cache_if_block_final: block_number
) do
item = scope.first
uri_obj = item.parsed_data_uri
send_data(uri_obj.decoded_data, type: uri_obj.mimetype, disposition: 'inline')
end
end
def data_options
# Handle CORS preflight requests for the data endpoint
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization'
response.headers['Access-Control-Max-Age'] = '3600'
response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin'
head :ok
end
def attachment
scope = Ethscription.all
id_or_hash = params[:id].to_s.downcase
scope = id_or_hash.match?(/\A0x[0-9a-f]{64}\z/) ?
scope.where(transaction_hash: id_or_hash) :
scope.where(ethscription_number: id_or_hash)
sha, blockhash, block_number = scope.pick(:attachment_sha, :block_blockhash, :block_number)
attachment_scope = EthscriptionAttachment.where(sha: sha)
unless attachment_scope.exists?
# Ensure CORS headers are set even for 404 responses
if request.headers['Origin']
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization'
response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin'
end
head 404
return
end
response.headers.delete('X-Frame-Options')
# Ensure CORS headers are set for cross-origin requests
if request.headers['Origin']
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization'
response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin'
end
set_cache_control_headers(
max_age: 6,
s_max_age: 1.minute,
etag: [sha, blockhash],
extend_cache_if_block_final: block_number
) do
attachment = attachment_scope.first
send_data(attachment.content, type: attachment.content_type_with_encoding, disposition: 'inline')
end
end
def attachment_options
# Handle CORS preflight requests for the attachment endpoint
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization'
response.headers['Access-Control-Max-Age'] = '3600'
response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin'
head :ok
end
def exists
existing = Ethscription.find_by_content_sha(params[:sha])
render json: {
result: {
exists: existing.present?,
ethscription: existing
}
}
end
def exists_multi
shas = Array.wrap(params[:shas]).sort.uniq
if shas.size > 100
render json: { error: "Too many SHAs" }, status: 400
return
end
result = Rails.cache.fetch(["ethscription-api-exists-multi", shas], expires_in: 12.seconds) do
existing_ethscriptions = Ethscription.where(content_sha: shas).pluck(:content_sha, :transaction_hash)
sha_to_transaction_hash = existing_ethscriptions.to_h
shas.each do |sha|
sha_to_transaction_hash[sha] ||= nil
end
sha_to_transaction_hash
end
render json: { result: result }
end
def newer_ethscriptions
mimetypes = params[:mimetypes] || []
initial_owner = params[:initial_owner]
requested_block_number = params[:block_number].to_i
client_past_ethscriptions_count = params[:past_ethscriptions_count]
past_ethscriptions_checksum = params[:past_ethscriptions_checksum]
system_max_ethscriptions = ENV.fetch('MAX_ETHSCRIPTIONS_PER_VM_REQUEST', 25).to_i
system_max_blocks = ENV.fetch('MAX_BLOCKS_PER_VM_REQUEST', 50).to_i
max_ethscriptions = [params[:max_ethscriptions]&.to_i || 50, system_max_ethscriptions].min
max_blocks = [params[:max_blocks]&.to_i || 500, system_max_blocks].min
scope = Ethscription.all.oldest_first
scope = scope.where(mimetype: mimetypes) if mimetypes.present?
scope = scope.where(initial_owner: initial_owner) if initial_owner.present?
unless scope.exists?
render json: {
error: {
message: "No matching ethscriptions found",
resolution: :retry_with_delay
}
}, status: :unprocessable_entity
return
end
requested_block_number = [requested_block_number, scope.limit(1).pluck(:block_number).first].max
we_are_up_to_date = EthBlock.where(block_number: requested_block_number).
where.not(imported_at: nil).exists?
unless we_are_up_to_date
render json: {
error: {
message: "Block not yet imported. Please try again later",
resolution: :retry
}
}, status: :unprocessable_entity
return
end
last_ethscription_block = scope.where('block_number >= ? AND block_number < ?',
requested_block_number,
requested_block_number + max_blocks)
.order(:block_number, :transaction_index)
.offset(max_ethscriptions - 1)
.pluck(:block_number)
.first
last_block_in_range = last_ethscription_block || requested_block_number + max_blocks - 1
last_block_in_range -= 1 if last_block_in_range > requested_block_number
block_range = (requested_block_number..last_block_in_range).to_a
all_blocks_in_range = EthBlock.where(block_number: block_range).where.not(imported_at: nil).order(:block_number).index_by(&:block_number)
if all_blocks_in_range[requested_block_number].nil?
render json: {
error: {
message: "Block not yet imported. Please try again later",
resolution: :retry
}
}, status: :unprocessable_entity
return
end
ethscriptions_in_range = scope.where(block_number: block_range)
ethscriptions_by_block = ethscriptions_in_range.group_by(&:block_number)
block_data = all_blocks_in_range.map do |block_number, block|
current_block_ethscriptions = ethscriptions_by_block[block_number] || []
{
blockhash: block.blockhash,
parent_blockhash: block.parent_blockhash,
block_number: block.block_number,
timestamp: block.timestamp.to_i,
ethscriptions: current_block_ethscriptions
}
end
total_ethscriptions_in_future_blocks = scope.where('block_number > ?', block_range.last).count
render json: {
total_future_ethscriptions: total_ethscriptions_in_future_blocks,
blocks: block_data
}
end
end