immudb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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