immudb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,366 @@
1
+ # Copyright 2021 CodeNotary, Inc. All rights reserved.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ module Immudb
14
+ class Client
15
+ def initialize(url: nil, host: nil, port: nil, username: nil, password: nil, database: nil, timeout: nil, rs: nil)
16
+ url ||= ENV["IMMUDB_URL"]
17
+ if url
18
+ uri = URI.parse(url)
19
+
20
+ if uri.scheme != "immudb"
21
+ raise ArgumentError, "Expected url to start with immudb://"
22
+ end
23
+
24
+ host ||= uri.host
25
+ port ||= uri.port
26
+ username ||= uri.username
27
+ password ||= uri.password
28
+ database ||= uri.path.sub(/\A\//, "")
29
+ end
30
+
31
+ host ||= "localhost"
32
+ port ||= 3322
33
+ username ||= "immudb"
34
+ password ||= "immudb"
35
+ database ||= "defaultdb"
36
+
37
+ raise ArgumentError, "Invalid host" if host.include?(":")
38
+
39
+ @rs = rs || RootService.new
40
+
41
+ @interceptor = Interceptor.new
42
+ @grpc = Schema::ImmuService::Stub.new("#{host}:#{port.to_i}", :this_channel_is_insecure, timeout: timeout, interceptors: [interceptor])
43
+
44
+ login(username, password)
45
+ use_database(database)
46
+ end
47
+
48
+ def list_users
49
+ permission_map = PERMISSION.invert
50
+ grpc.list_users(Google::Protobuf::Empty.new).users.map do |user|
51
+ {
52
+ user: user.user,
53
+ permissions: user.permissions.map { |v| {database: v.database, permission: permission_map.fetch(v.permission)} },
54
+ created_by: user.createdby,
55
+ created_at: Time.parse(user.createdat),
56
+ active: user.active
57
+ }
58
+ end
59
+ end
60
+
61
+ def create_user(user, password:, permission:, database:)
62
+ req = Schema::CreateUserRequest.new(user: user, password: password, permission: PERMISSION.fetch(permission), database: database)
63
+ grpc.create_user(req)
64
+ nil
65
+ end
66
+
67
+ def change_password(user, old_password:, new_password:)
68
+ req = Schema::ChangePasswordRequest.new(user: user, oldPassword: old_password, newPassword: new_password)
69
+ grpc.change_password(req)
70
+ nil
71
+ end
72
+
73
+ def set(key, value)
74
+ req = Schema::SetRequest.new(KVs: [Schema::KeyValue.new(key: key, value: value)])
75
+ grpc.set(req)
76
+ nil
77
+ end
78
+
79
+ def verified_set(key, value)
80
+ state = @rs.get
81
+ kv = Schema::KeyValue.new(key: key, value: value)
82
+
83
+ raw_request = Schema::VerifiableSetRequest.new(
84
+ setRequest: Schema::SetRequest.new(KVs: [kv]),
85
+ proveSinceTx: state.txId
86
+ )
87
+ verifiable_tx = grpc.verifiable_set(raw_request)
88
+ tx = Store.tx_from(verifiable_tx.tx)
89
+ inclusion_proof = tx.proof(SET_KEY_PREFIX + key)
90
+ ekv = Store.encode_kv(key, value)
91
+ verifies = Store.verify_inclusion(inclusion_proof, ekv.digest, tx.eh)
92
+ unless verifies
93
+ raise VerificationError
94
+ end
95
+ if tx.eh != Store.digest_from(verifiable_tx.dualProof.targetTxMetadata.eH)
96
+ raise VerificationError
97
+ end
98
+ if state.txId == 0
99
+ source_id = tx.ID
100
+ source_alh = tx.Alh
101
+ else
102
+ source_id = state.txId
103
+ source_alh = Store.digest_from(state.txHash)
104
+ end
105
+ target_id = tx.ID
106
+ target_alh = tx.Alh
107
+
108
+ verifies = Store.verify_dual_proof(
109
+ HTree.dual_proof_from(verifiable_tx.dualProof),
110
+ source_id,
111
+ target_id,
112
+ source_alh,
113
+ target_alh
114
+ )
115
+ if !verifies
116
+ raise VerificationError
117
+ end
118
+ newstate = State.new(
119
+ db: state.db,
120
+ txId: target_id,
121
+ txHash: target_alh,
122
+ publicKey: verifiable_tx.signature&.publicKey,
123
+ signature: verifiable_tx.signature&.signature,
124
+ )
125
+ if !verifying_key.nil?
126
+ newstate.verify(verifying_key)
127
+ end
128
+ @rs.set(newstate)
129
+ nil
130
+ end
131
+
132
+ def get(key)
133
+ req = Schema::KeyRequest.new(key: key)
134
+ grpc.get(req).value
135
+ end
136
+
137
+ def verified_get(key)
138
+ state = @rs.get
139
+ req = Schema::VerifiableGetRequest.new(
140
+ keyRequest: Schema::KeyRequest.new(key: key),
141
+ proveSinceTx: state.txId
142
+ )
143
+ ventry = grpc.verifiable_get(req)
144
+
145
+ inclusion_proof = HTree.inclusion_proof_from(ventry.inclusionProof)
146
+ dual_proof = HTree.dual_proof_from(ventry.verifiableTx.dualProof)
147
+
148
+ if ventry.entry.referencedBy.nil? || ventry.entry.referencedBy.key == ""
149
+ vTx = ventry.entry.tx
150
+ kv = Store.encode_kv(key, ventry.entry.value)
151
+ else
152
+ vTx = ventry.entry.referencedBy.tx
153
+ kv = store.encode_reference(ventry.entry.referencedBy.key, ventry.entry.key, ventry.entry.referencedBy.atTx)
154
+ end
155
+
156
+ if state.txId <= vTx
157
+ eh = Store.digest_from(ventry.verifiableTx.dualProof.targetTxMetadata.eH)
158
+ source_id = state.txId
159
+ source_alh = Store.digest_from(state.txHash)
160
+ target_id = vTx
161
+ target_alh = dual_proof.targetTxMetadata.alh
162
+ else
163
+ eh = Store.digest_from(ventry.verifiableTx.dualProof.sourceTxMetadata.eH)
164
+ source_id = vTx
165
+ source_alh = dual_proof.sourceTxMetadata.alh
166
+ target_id = state.txId
167
+ target_alh = store.digest_from(state.txHash)
168
+ end
169
+
170
+ verifies = Store.verify_inclusion(inclusion_proof, kv.digest, eh)
171
+ if !verifies
172
+ raise VerificationError
173
+ end
174
+ verifies = Store.verify_dual_proof(
175
+ dual_proof,
176
+ source_id,
177
+ target_id,
178
+ source_alh,
179
+ target_alh)
180
+ if !verifies
181
+ raise VerificationError
182
+ end
183
+ newstate = State.new(
184
+ db: state.db,
185
+ txId: target_id,
186
+ txHash: target_alh,
187
+ publicKey: ventry.verifiableTx.signature&.publicKey,
188
+ signature: ventry.verifiableTx.signature&.signature,
189
+ )
190
+ if !verifying_key.nil?
191
+ newstate.verify(verifying_key)
192
+ end
193
+ @rs.set(newstate)
194
+
195
+ ventry.entry.value
196
+ end
197
+
198
+ def set_all(values)
199
+ req = Schema::SetRequest.new(KVs: values.map { |k, v| Schema::KeyValue.new(key: k, value: v) })
200
+ grpc.set(req)
201
+ nil
202
+ end
203
+
204
+ def get_all(keys)
205
+ req = Schema::KeyListRequest.new(keys: keys)
206
+ grpc.get_all(req).entries.to_h { |v| [v.key, v.value] }
207
+ end
208
+
209
+ def create_database(name)
210
+ req = Schema::Database.new(databaseName: name)
211
+ grpc.create_database(req)
212
+ nil
213
+ end
214
+
215
+ def list_databases
216
+ grpc.database_list(Google::Protobuf::Empty.new).databases.map(&:databaseName)
217
+ end
218
+
219
+ def use_database(name)
220
+ req = Schema::Database.new(databaseName: name)
221
+ res = grpc.use_database(req)
222
+ interceptor.token = res.token
223
+ @rs.init(name, grpc)
224
+ nil
225
+ end
226
+
227
+ # history
228
+
229
+ def history(key, offset: nil, limit: nil, desc: false)
230
+ req = Schema::HistoryRequest.new(key: key, offset: offset, limit: limit, desc: desc)
231
+ grpc.history(req).entries.map do |entry|
232
+ {
233
+ tx: entry.tx,
234
+ value: entry.value
235
+ }
236
+ end
237
+ end
238
+
239
+ def scan(seek_key: nil, prefix: nil, desc: false, limit: nil, since_tx: nil, no_wait: false)
240
+ req = Schema::ScanRequest.new(seekKey: seek_key, prefix: prefix, desc: desc, limit: limit, sinceTx: since_tx, noWait: no_wait)
241
+ grpc.scan(req).entries.map do |entry|
242
+ {
243
+ tx: entry.tx,
244
+ key: entry.key,
245
+ value: entry.value
246
+ }
247
+ end
248
+ end
249
+
250
+ # management
251
+
252
+ def clean_index
253
+ grpc.compact_index(Google::Protobuf::Empty.new)
254
+ nil
255
+ end
256
+
257
+ def healthy?
258
+ grpc.health(Google::Protobuf::Empty.new).status
259
+ rescue Error
260
+ false
261
+ end
262
+
263
+ def version
264
+ grpc.health(Google::Protobuf::Empty.new).version
265
+ end
266
+
267
+ # sql
268
+
269
+ def sql_exec(sql, params = {})
270
+ req = Schema::SQLExecRequest.new(sql: sql, params: sql_params(params))
271
+ grpc.sql_exec(req)
272
+ nil
273
+ end
274
+
275
+ def sql_query(sql, params = {})
276
+ req = Schema::SQLQueryRequest.new(sql: sql, params: sql_params(params))
277
+ res = grpc.sql_query(req)
278
+ sql_result(res)
279
+ end
280
+
281
+ def list_tables
282
+ grpc.list_tables(Google::Protobuf::Empty.new).rows.map { |r| r.values.first.s }
283
+ end
284
+
285
+ def describe_table(name)
286
+ req = Schema::Table.new(tableName: name)
287
+ res = grpc.describe_table(req)
288
+ sql_result(res).to_a
289
+ end
290
+
291
+ # hide token
292
+ def inspect
293
+ to_s
294
+ end
295
+
296
+ private
297
+
298
+ def grpc
299
+ @grpc
300
+ end
301
+
302
+ def interceptor
303
+ @interceptor
304
+ end
305
+
306
+ def verifying_key
307
+ nil
308
+ end
309
+
310
+ # keep private for now
311
+ # if public, need to call use_database
312
+ def login(user, password)
313
+ req = Schema::LoginRequest.new(user: user, password: password)
314
+ res = grpc.login(req)
315
+ interceptor.token = res.token
316
+
317
+ # warn "[immudb] #{res.warning}" if res.warning
318
+
319
+ res
320
+ end
321
+
322
+ def logout
323
+ res = grpc.logout(Google::Protobuf::Empty.new)
324
+ interceptor.token = nil
325
+ res
326
+ end
327
+
328
+ def sql_params(params)
329
+ params.map do |k, v|
330
+ Schema::NamedParam.new(name: k, value: sql_value(v))
331
+ end
332
+ end
333
+
334
+ def sql_value(v)
335
+ opts =
336
+ case v
337
+ when nil
338
+ {null: :NULL_VALUE}
339
+ when Integer
340
+ {n: v}
341
+ when String
342
+ {s: v}
343
+ when true, false
344
+ {b: v}
345
+ else
346
+ raise ArgumentError, "Unsupported param type: #{v.class.name}"
347
+ end
348
+ Schema::SQLValue.new(**opts)
349
+ end
350
+
351
+ def sql_result(res)
352
+ columns = res.columns.map { |v| sql_column_name(v.name) }
353
+ rows = res.rows.map { |r| r.values.map { |v| v.value == :null ? nil : v.send(v.value) } }
354
+ column_types = res.columns.to_h { |v| [v.name, v.type] }
355
+ SqlResult.new(columns, rows, column_types)
356
+ end
357
+
358
+ def sql_column_name(name)
359
+ if name.start_with?("(") && name.end_with?(")")
360
+ name.split(".").last.chomp(")")
361
+ else
362
+ name
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,34 @@
1
+ # Copyright 2021 CodeNotary, Inc. All rights reserved.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ module Immudb
14
+ LEAF_PREFIX = "\x00".b
15
+ NODE_PREFIX = "\x01".b
16
+ ROOT_CACHE_PATH = ".immudbRoot"
17
+
18
+ PERMISSION = {
19
+ sys_admin: 255,
20
+ admin: 254,
21
+ none: 0,
22
+ read: 1,
23
+ read_write: 2
24
+ }
25
+
26
+ SET_KEY_PREFIX = "\x00".b
27
+ SORTED_KEY_PREFIX = "\x01".b
28
+
29
+ PLAIN_VALUE_PREFIX = "\x00".b
30
+ REFERENCE_VALUE_PREFIX = "\x01".b
31
+
32
+ OLDEST_FIRST = false
33
+ NEWEST_FIRST = true
34
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright 2021 CodeNotary, Inc. All rights reserved.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ module Immudb
14
+ class DualProof
15
+ attr_accessor :sourceTxMetadata, :targetTxMetadata, :inclusionProof, :consistencyProof, :targetBlTxAlh, :lastInclusionProof, :linearProof
16
+ end
17
+ end