Skip to content

Commit 8838c15

Browse files
authored
Merge pull request #66 from firstbatchxyz/erhant/set-setmany
`set` and `setMany` added
2 parents 95c39ac + 25e68e3 commit 8838c15

File tree

8 files changed

+476
-3
lines changed

8 files changed

+476
-3
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hollowdb",
3-
"version": "1.3.3",
3+
"version": "1.3.4",
44
"description": "A decentralized privacy-preserving key-value database",
55
"license": "MIT",
66
"homepage": "https://github.com/firstbatchxyz/hollowdb#readme",
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
2+
// src/contracts/errors/index.ts
3+
var KeyExistsError = new ContractError("Key already exists.");
4+
var KeyNotExistsError = new ContractError("Key does not exist.");
5+
var CantEvolveError = new ContractError("Evolving is disabled.");
6+
var NoVerificationKeyError = new ContractError("No verification key.");
7+
var UnknownProtocolError = new ContractError("Unknown protocol.");
8+
var NotWhitelistedError = new ContractError("Not whitelisted.");
9+
var InvalidProofError = new ContractError("Invalid proof.");
10+
var ExpectedProofError = new ContractError("Expected a proof.");
11+
var NullValueError = new ContractError("Value cant be null, use remove instead.");
12+
var NotOwnerError = new ContractError("Not contract owner.");
13+
var InvalidFunctionError = new ContractError("Invalid function.");
14+
var ArrayLengthMismatchError = new ContractError("Key and value counts mismatch.");
15+
16+
// src/contracts/utils/index.ts
17+
var verifyProof = async (proof, psignals, verificationKey) => {
18+
if (!verificationKey) {
19+
throw NoVerificationKeyError;
20+
}
21+
if (verificationKey.protocol !== "groth16" && verificationKey.protocol !== "plonk") {
22+
throw UnknownProtocolError;
23+
}
24+
return await SmartWeave.extensions[verificationKey.protocol].verify(verificationKey, psignals, proof);
25+
};
26+
var hashToGroup = (value) => {
27+
if (value) {
28+
return BigInt(SmartWeave.extensions.ethers.utils.ripemd160(Buffer.from(JSON.stringify(value))));
29+
} else {
30+
return BigInt(0);
31+
}
32+
};
33+
34+
// src/contracts/modifiers/index.ts
35+
var onlyOwner = (caller, input, state) => {
36+
if (caller !== state.owner) {
37+
throw NotOwnerError;
38+
}
39+
return input;
40+
};
41+
var onlyNonNullValue = (_, input) => {
42+
if (input.value === null) {
43+
throw NullValueError;
44+
}
45+
return input;
46+
};
47+
var onlyNonNullValues = (_, input) => {
48+
if (input.values.some((val) => val === null)) {
49+
throw NullValueError;
50+
}
51+
return input;
52+
};
53+
var onlyWhitelisted = (list) => {
54+
return (caller, input, state) => {
55+
if (!state.isWhitelistRequired[list]) {
56+
return input;
57+
}
58+
if (!state.whitelists[list][caller]) {
59+
throw NotWhitelistedError;
60+
}
61+
return input;
62+
};
63+
};
64+
var onlyProofVerified = (proofName, prepareInputs) => {
65+
return async (caller, input, state) => {
66+
if (!state.isProofRequired[proofName]) {
67+
return input;
68+
}
69+
if (!input.proof) {
70+
throw ExpectedProofError;
71+
}
72+
const ok = await verifyProof(
73+
input.proof,
74+
await prepareInputs(caller, input, state),
75+
state.verificationKeys[proofName]
76+
);
77+
if (!ok) {
78+
throw InvalidProofError;
79+
}
80+
return input;
81+
};
82+
};
83+
async function apply(caller, input, state, ...modifiers) {
84+
for (const modifier of modifiers) {
85+
input = await modifier(caller, input, state);
86+
}
87+
return input;
88+
}
89+
90+
// src/contracts/hollowdb-set.contract.ts
91+
var handle = async (state, action) => {
92+
const { caller, input } = action;
93+
switch (input.function) {
94+
case "get": {
95+
const { key } = await apply(caller, input.value, state);
96+
return { result: await SmartWeave.kv.get(key) };
97+
}
98+
case "getMany": {
99+
const { keys } = await apply(caller, input.value, state);
100+
const values = await Promise.all(keys.map((key) => SmartWeave.kv.get(key)));
101+
return { result: values };
102+
}
103+
case "set": {
104+
const { key, value } = await apply(caller, input.value, state, onlyWhitelisted("set"), onlyNonNullValue);
105+
await SmartWeave.kv.put(key, value);
106+
return { state };
107+
}
108+
case "setMany": {
109+
const { keys, values } = await apply(caller, input.value, state, onlyWhitelisted("set"), onlyNonNullValues);
110+
if (keys.length !== values.length) {
111+
throw new ContractError("Key and value counts mismatch");
112+
}
113+
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
114+
return { state };
115+
}
116+
case "getKeys": {
117+
const { options } = await apply(caller, input.value, state);
118+
return { result: await SmartWeave.kv.keys(options) };
119+
}
120+
case "getKVMap": {
121+
const { options } = await apply(caller, input.value, state);
122+
return { result: await SmartWeave.kv.kvMap(options) };
123+
}
124+
case "put": {
125+
const { key, value } = await apply(caller, input.value, state, onlyWhitelisted("put"), onlyNonNullValue);
126+
if (await SmartWeave.kv.get(key) !== null) {
127+
throw KeyExistsError;
128+
}
129+
await SmartWeave.kv.put(key, value);
130+
return { state };
131+
}
132+
case "putMany": {
133+
const { keys, values } = await apply(caller, input.value, state, onlyWhitelisted("put"), onlyNonNullValues);
134+
if (keys.length !== values.length) {
135+
throw new ContractError("Key and value counts mismatch");
136+
}
137+
if (await Promise.all(keys.map((key) => SmartWeave.kv.get(key))).then((values2) => values2.some((val) => val !== null))) {
138+
throw KeyExistsError;
139+
}
140+
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
141+
return { state };
142+
}
143+
case "update": {
144+
const { key, value } = await apply(
145+
caller,
146+
input.value,
147+
state,
148+
onlyNonNullValue,
149+
onlyWhitelisted("update"),
150+
onlyProofVerified("auth", async (_, input2) => {
151+
const oldValue = await SmartWeave.kv.get(input2.key);
152+
return [hashToGroup(oldValue), hashToGroup(input2.value), BigInt(input2.key)];
153+
})
154+
);
155+
await SmartWeave.kv.put(key, value);
156+
return { state };
157+
}
158+
case "remove": {
159+
const { key } = await apply(
160+
caller,
161+
input.value,
162+
state,
163+
onlyWhitelisted("update"),
164+
onlyProofVerified("auth", async (_, input2) => {
165+
const oldValue = await SmartWeave.kv.get(input2.key);
166+
return [hashToGroup(oldValue), BigInt(0), BigInt(input2.key)];
167+
})
168+
);
169+
await SmartWeave.kv.del(key);
170+
return { state };
171+
}
172+
case "updateOwner": {
173+
const { newOwner } = await apply(caller, input.value, state, onlyOwner);
174+
state.owner = newOwner;
175+
return { state };
176+
}
177+
case "updateProofRequirement": {
178+
const { name, value } = await apply(caller, input.value, state, onlyOwner);
179+
state.isProofRequired[name] = value;
180+
return { state };
181+
}
182+
case "updateVerificationKey": {
183+
const { name, verificationKey } = await apply(caller, input.value, state, onlyOwner);
184+
state.verificationKeys[name] = verificationKey;
185+
return { state };
186+
}
187+
case "updateWhitelistRequirement": {
188+
const { name, value } = await apply(caller, input.value, state, onlyOwner);
189+
state.isWhitelistRequired[name] = value;
190+
return { state };
191+
}
192+
case "updateWhitelist": {
193+
const { add, remove, name } = await apply(caller, input.value, state, onlyOwner);
194+
add.forEach((user) => {
195+
state.whitelists[name][user] = true;
196+
});
197+
remove.forEach((user) => {
198+
delete state.whitelists[name][user];
199+
});
200+
return { state };
201+
}
202+
case "evolve": {
203+
const srcTxId = await apply(caller, input.value, state, onlyOwner);
204+
if (!state.canEvolve) {
205+
throw CantEvolveError;
206+
}
207+
state.evolve = srcTxId;
208+
return { state };
209+
}
210+
default:
211+
input;
212+
throw InvalidFunctionError;
213+
}
214+
};
215+
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import {CantEvolveError, InvalidFunctionError, KeyExistsError} from './errors';
2+
import {apply, onlyNonNullValue, onlyNonNullValues, onlyOwner, onlyProofVerified, onlyWhitelisted} from './modifiers';
3+
import type {ContractHandle} from './types';
4+
import {hashToGroup} from './utils';
5+
6+
type Mode = {proofs: ['auth']; whitelists: ['put', 'update', 'set']};
7+
type Value = unknown;
8+
9+
export type SetInput<V> = {
10+
function: 'set';
11+
value: {
12+
key: string;
13+
value: V;
14+
};
15+
};
16+
17+
export type SetManyInput<V> = {
18+
function: 'setMany';
19+
value: {
20+
keys: string[];
21+
values: V[];
22+
};
23+
};
24+
25+
export const handle: ContractHandle<Value, Mode, SetInput<Value> | SetManyInput<Value>> = async (state, action) => {
26+
const {caller, input} = action;
27+
switch (input.function) {
28+
case 'get': {
29+
const {key} = await apply(caller, input.value, state);
30+
return {result: (await SmartWeave.kv.get(key)) as Value | null};
31+
}
32+
33+
case 'getMany': {
34+
const {keys} = await apply(caller, input.value, state);
35+
const values = (await Promise.all(keys.map(key => SmartWeave.kv.get(key)))) as (Value | null)[];
36+
return {result: values};
37+
}
38+
39+
case 'set': {
40+
const {key, value} = await apply(caller, input.value, state, onlyWhitelisted('set'), onlyNonNullValue);
41+
await SmartWeave.kv.put(key, value);
42+
return {state};
43+
}
44+
45+
case 'setMany': {
46+
const {keys, values} = await apply(caller, input.value, state, onlyWhitelisted('set'), onlyNonNullValues);
47+
if (keys.length !== values.length) {
48+
throw new ContractError('Key and value counts mismatch');
49+
}
50+
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
51+
return {state};
52+
}
53+
54+
case 'getKeys': {
55+
const {options} = await apply(caller, input.value, state);
56+
return {result: await SmartWeave.kv.keys(options)};
57+
}
58+
59+
case 'getKVMap': {
60+
const {options} = await apply(caller, input.value, state);
61+
return {result: await SmartWeave.kv.kvMap(options)};
62+
}
63+
64+
case 'put': {
65+
const {key, value} = await apply(caller, input.value, state, onlyWhitelisted('put'), onlyNonNullValue);
66+
if ((await SmartWeave.kv.get(key)) !== null) {
67+
throw KeyExistsError;
68+
}
69+
await SmartWeave.kv.put(key, value);
70+
return {state};
71+
}
72+
73+
case 'putMany': {
74+
const {keys, values} = await apply(caller, input.value, state, onlyWhitelisted('put'), onlyNonNullValues);
75+
if (keys.length !== values.length) {
76+
throw new ContractError('Key and value counts mismatch');
77+
}
78+
79+
if (await Promise.all(keys.map(key => SmartWeave.kv.get(key))).then(values => values.some(val => val !== null))) {
80+
throw KeyExistsError;
81+
}
82+
await Promise.all(keys.map((key, i) => SmartWeave.kv.put(key, values[i])));
83+
return {state};
84+
}
85+
86+
case 'update': {
87+
const {key, value} = await apply(
88+
caller,
89+
input.value,
90+
state,
91+
onlyNonNullValue,
92+
onlyWhitelisted('update'),
93+
onlyProofVerified('auth', async (_, input) => {
94+
const oldValue = await SmartWeave.kv.get(input.key);
95+
return [hashToGroup(oldValue), hashToGroup(input.value), BigInt(input.key)];
96+
})
97+
);
98+
await SmartWeave.kv.put(key, value);
99+
return {state};
100+
}
101+
102+
case 'remove': {
103+
const {key} = await apply(
104+
caller,
105+
input.value,
106+
state,
107+
onlyWhitelisted('update'),
108+
onlyProofVerified('auth', async (_, input) => {
109+
const oldValue = await SmartWeave.kv.get(input.key);
110+
return [hashToGroup(oldValue), BigInt(0), BigInt(input.key)];
111+
})
112+
);
113+
await SmartWeave.kv.del(key);
114+
return {state};
115+
}
116+
117+
case 'updateOwner': {
118+
const {newOwner} = await apply(caller, input.value, state, onlyOwner);
119+
state.owner = newOwner;
120+
return {state};
121+
}
122+
123+
case 'updateProofRequirement': {
124+
const {name, value} = await apply(caller, input.value, state, onlyOwner);
125+
state.isProofRequired[name] = value;
126+
return {state};
127+
}
128+
129+
case 'updateVerificationKey': {
130+
const {name, verificationKey} = await apply(caller, input.value, state, onlyOwner);
131+
state.verificationKeys[name] = verificationKey;
132+
return {state};
133+
}
134+
135+
case 'updateWhitelistRequirement': {
136+
const {name, value} = await apply(caller, input.value, state, onlyOwner);
137+
state.isWhitelistRequired[name] = value;
138+
return {state};
139+
}
140+
141+
case 'updateWhitelist': {
142+
const {add, remove, name} = await apply(caller, input.value, state, onlyOwner);
143+
add.forEach(user => {
144+
state.whitelists[name][user] = true;
145+
});
146+
remove.forEach(user => {
147+
delete state.whitelists[name][user];
148+
});
149+
return {state};
150+
}
151+
152+
case 'evolve': {
153+
const srcTxId = await apply(caller, input.value, state, onlyOwner);
154+
if (!state.canEvolve) {
155+
throw CantEvolveError;
156+
}
157+
state.evolve = srcTxId;
158+
return {state};
159+
}
160+
161+
default:
162+
// type-safe way to make sure all switch cases are handled
163+
input satisfies never;
164+
throw InvalidFunctionError;
165+
}
166+
};

0 commit comments

Comments
 (0)