Skip to content
This repository has been archived by the owner on Jan 23, 2025. It is now read-only.

Commit

Permalink
Merge pull request #7 from zama-ai/petar/decryptions
Browse files Browse the repository at this point in the history
Support integer decryptions
  • Loading branch information
dartdart26 authored Aug 1, 2023
2 parents b8f6340 + 3cc5065 commit abcd206
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 128 deletions.
32 changes: 16 additions & 16 deletions .github/workflows/publish_docker_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,29 @@
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.

name: Publish fhevm-requires-db image
name: Publish fhevm-decryptions-db image

on:
workflow_dispatch:
inputs:
fhevm_requires_db_tag:
description: 'fhevm-requires-db tag (default: latest)'
fhevm_decryptions_db_tag:
description: 'fhevm-decryptions-db tag (default: latest)'
default: "latest"
type: string
required: true
refToBuildFhevmRequiresDb:
description: 'Branch, tag or commit SHA1 to checkout fhevm-requires-db'
refToBuildFhevmDecryptionsDb:
description: 'Branch, tag or commit SHA1 to checkout fhevm-decryptions-db'
required: true
default: "main"
type: string
refToDockerfileFhevmRequiresDb:
description: 'Branch, tag or commit SHA1 to checkout fhevm-requires-db (Dockerfile)'
refToDockerfileFhevmDecryptionsDb:
description: 'Branch, tag or commit SHA1 to checkout fhevm-decryptions-db (Dockerfile)'
required: true
default: "main"
type: string

env:
DOCKER_IMAGE: ghcr.io/zama-ai/fhevm-requires-db
DOCKER_IMAGE: ghcr.io/zama-ai/fhevm-decryptions-db

jobs:
push_to_registry:
Expand All @@ -39,13 +39,13 @@ jobs:
- name: Check out the repo for build
uses: actions/checkout@v3
with:
path: fhevm-requires-db
ref: ${{ inputs.refToBuildFhevmRequiresDb }}
path: fhevm-decryptions-db
ref: ${{ inputs.refToBuildFhevmDecryptionsDb }}
- name: Check out the repo for build
uses: actions/checkout@v3
with:
path: fhevm-requires-db-dockerfile
ref: ${{ inputs.refToDockerfileFhevmRequiresDb }}
path: fhevm-decryptions-db-dockerfile
ref: ${{ inputs.refToDockerfileFhevmDecryptionsDb }}

- name: Login to GitHub Container Registry
uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b
Expand All @@ -63,8 +63,8 @@ jobs:
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
platforms: linux/amd64,linux/arm64
context: ./fhevm-requires-db
file: ./fhevm-requires-db-dockerfile/Dockerfile
context: ./fhevm-decryptions-db
file: ./fhevm-decryptions-db-dockerfile/Dockerfile
push: true
tags: ${{ env.DOCKER_IMAGE }}:${{ inputs.fhevm_requires_db_tag }},${{ env.DOCKER_IMAGE }}:latest
labels: fhevm-requires-db
tags: ${{ env.DOCKER_IMAGE }}:${{ inputs.fhevm_decryptions_db_tag }},${{ env.DOCKER_IMAGE }}:latest
labels: fhevm-decryptions-db
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "fhevm-requires-db"
name = "fhevm-decryptions-db"
version = "0.1.0"
edition = "2021"

Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ FROM rust:1.70-slim-bullseye
WORKDIR /usr/local/app
RUN apt-get update && apt-get install -y clang
RUN apt-get install libc6 -y
COPY --from=builder /usr/local/app/target/release/fhevm-requires-db .
COPY --from=builder /usr/local/app/target/release/fhevm-decryptions-db .
COPY --from=builder /usr/local/app/Rocket.toml .

EXPOSE 8001/tcp

CMD ["/usr/local/app/fhevm-requires-db"]
CMD ["/usr/local/app/fhevm-decryptions-db"]
47 changes: 25 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,67 @@
# fhEVM Requires Database
# fhEVM Decryptions Database

The fhEVM Requires Database is a key-value database of require statement results plus a signature for them from the decryption oracle (or just oracle). It supports two operations:
* HTTP GET - executed by validators, full nodes or anyone in order to get a signed require result from the DB
* HTTP PUT - only executed by the oracle in order to put a signed require result in the DB
The fhEVM Decryptions Database is a key-value database of decryption results plus a signature for them from decryption oracles (or just oracles). It supports two operations:
* HTTP GET - executed by validators, full nodes or anyone in order to get a signed decryption result from the DB
* HTTP PUT - only executed by oracles in order to put a signed decryption result in the DB

Access control is left to an external HTTP service (e.g. a proxy) that sits in front of the DB. For example, one might allow GET requests from the Internet and only allow PUT request from a local oracle address. Furthermore, TLS handling is also left to the external HTTP service.
Access control is left to an external HTTP service (e.g. a proxy) that sits in front of the DB. For example, one might allow GET requests from the Internet and only allow PUT request from oracles. Furthermore, TLS handling is also left to the external HTTP service.

Another point is that the intention for the database is to be as simple as possible, without trying to interpret the data it stores. For example, it only expects that the keys are 32 byte hashes and it doesn't impose anything on signatures. Rationale is that there is external access control such that only the trusted oracle can write to the DB through HTTP PUT.
Another point is that the intention for the database is to be as simple as possible, without trying to interpret the data it stores. For example, it only expects that the keys are 32 byte hashes and it doesn't impose anything on signatures. Rationale is that there is external access control such that only trusted oracles can write to the DB through HTTP PUT.

Currently, the DB doesn't support deletion of require results. That allows any node to catch up to the latest state from any previous point. If that approach proves problematic in terms of DB size and/or performance, we can consider pruning it in a future release.
Currently, the DB doesn't support deletion of decryption results. That allows any node to catch up to the latest state from any previous point. If that approach proves problematic in terms of DB size and/or performance, we can consider pruning it in a future release.

## API
The DB exposes a REST API on the `/require/<key>` route. The `key` parameter is a hex-encoded byte buffer of 32 bytes (i.e. 64 characters in hex).
The DB exposes a REST API on the `/decryption/<key>` route. The `key` parameter is a hex-encoded byte buffer of 32 bytes (i.e. 64 characters in hex).
The DB supports the following methods.

### HTTP PUT
The oracle can put a require result to the DB via an HTTP PUT request with a JSON payload. For example:
An oracle can put a decryption result to the DB via an HTTP PUT request with a JSON payload. For example:
```json
{
"value": true,
"value": 42,
"signature": "YmJiYg=="
}
```
The DB expects two fields:
* `value` - a bool value of the require
* `value` - an uint64 decrypted value
* `signature` - a base64-encoded signature

Example request:
```bash
curl -v --header "Content-type: application/json" --request PUT \
--data '{"value": true, "signature": "YmJiYg=="}' \
http://127.0.0.1:8001/require/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
--data '{"value": 42, "signature": "YmJiYg=="}' \
http://127.0.0.1:8001/decryption/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```

Anyone can get a require value via an HTTP GET request.
Anyone can get a decrypted value via an HTTP GET request.

Example request:
```bash
curl -v http://127.0.0.1:8001/require/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
curl -v http://127.0.0.1:8001/decryption/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```

The resulting response has the same JSON format as for HTTP PUT:
```json
{
"value": true,
"value": 42,
"signature": "YmJiYg=="
}
```

## Note on Multiple Oracles
As of now, there could be multiple oracles putting decryptions on the DB. However, only a single signature is allowed per decryption. This behaviour will be changed in a future release.

## Note on Determinism
It is expected that the oracle generates an unique key for a particular require. For example, the key could be the hash of the require's ciphertext. Based on that, the DB will overwrite a require value for an existing key, expecting that the "new" value is the same as the previous one. That allows the DB to not check the key for existence on every write. Though it is not expected to happen often, it might happen if, for example, the oracle crashes during execution.
It is expected that oracles generate a single unique key for a particular ciphertext. For example, the key could be the hash of the ciphertext.

## Note on Signature
The DB doesn't impose anything on the signature field other than it being valid base64. It is up to the blockchain protocol to decide what piece of data is signed. For example, one might do `sign(hash(require_ciphertext) || value)`.
The DB doesn't impose anything on the signature field other than it being valid base64. It is up to the blockchain protocol to decide what piece of data is signed. For example, one might do `sign(hash(ciphertext) || value)`.

## Note on RocksDB
We use RocksDB as an underlying key-value store. We've chosen it, because it is battle-tested, performant, in-process, tweakable and supports concurrent calls from multiple threads. If needed, it can easily be replaced with another store.

## Note on Race Conditions Between Oracle and Non-Oracle Nodes
Since the oracle is the only node that puts require results into the database and since all nodes (oracle and non-oracle ones) execute smart contract code at the same time, there is a race condition between the oracle putting a result and any other node reading it. Currently, the solution to this problem is to use a `WaitCache` that keeps pending key-values in memory for a limited period of time. Additionally, it allows a get request to wait until the requested key is put by the oracle.
Since oracles are the only nodes that put decryption results into the database and since all nodes (oracle and non-oracle ones) execute smart contract code at the same time, there is a race condition between an oracle putting a result and any other node reading it. Currently, the solution to this problem is to use a `WaitCache` that keeps pending key-values in memory for a limited period of time. Additionally, it allows a get request to wait until the requested key is put by the oracle.

## Build and Run
### Local
Expand All @@ -69,8 +72,8 @@ cargo run --release

### Docker
```bash
docker build -t fhevm-requires-db:latest .
docker run -d -p 8001:8001 fhevm-requires-db:latest
docker build -t fhevm-decryptions-db:latest .
docker run -d -p 8001:8001 fhevm-decryptions-db:latest
```

## Configuration
Expand All @@ -82,7 +85,7 @@ The following configuration options are currently supported:

`db_path` - A path to the RocksDB database.

`max_expected_oracle_delay_ms` - An HTTP GET might try to get a require that is not yet put by the oracle. This option configures the maximum time (in ms) that the oracle is expected to be late with the put operation.
`max_expected_oracle_delay_ms` - An HTTP GET might try to get a decryption that is not yet put by an oracle. This option configures the maximum time (in ms) that oracles are expected to be late with the put operation.

## Testing
Integration tests use a real RocksDB database. The database path is read from the `testing` profile in the configuration (Rocket.toml) file.
Expand Down
6 changes: 3 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use serde::Deserialize;
pub struct Config {
pub db_path: String,

/// A validator might try to get a require that is not yet put
/// by the oracle. This option configures the maximum time (in ms)
/// that the oracle is expected to be late with the put operation.
/// A validator might try to get a decryption that is not yet put
/// by an oracle. This option configures the maximum time (in ms)
/// that oracles are expected to be late with the put operation.
pub max_expected_oracle_delay_ms: u64,
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ pub fn configure_rocket(rocket: Rocket<Build>) -> Rocket<Build> {

let config: Config = figment.extract().expect("config");
let db = Arc::new(RocksDBStore::open(&config.db_path).expect("db open"));
let cache = Arc::new(WaitCache::<Vec<u8>, StoredRequire>::new(
let cache = Arc::new(WaitCache::<Vec<u8>, StoredDecryption>::new(
Duration::from_secs(config.max_expected_oracle_delay_ms),
));

rocket
.manage(db)
.manage(cache)
.mount("/", routes![put_require, get_require])
.mount("/", routes![put_decryption, get_decryption])
.attach(AdHoc::config::<Config>())
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#[macro_use]
extern crate rocket;

use fhevm_requires_db::build_and_configure_rocket;
use fhevm_decryptions_db::build_and_configure_rocket;

#[launch]
fn rocket() -> _ {
Expand Down
24 changes: 14 additions & 10 deletions src/rocksdb_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,41 @@ pub struct RocksDBStore {

impl RocksDBStore {
pub fn open(path: &str) -> Result<Self, Box<dyn Error>> {
let requires_cf_desc = ColumnFamilyDescriptor::new(Self::REQUIRES_CF, Options::default());
let decryptions_cf_desc =
ColumnFamilyDescriptor::new(Self::DECRYPTIONS_CF, Options::default());

let mut db_opts = Options::default();
db_opts.create_if_missing(true);
db_opts.create_missing_column_families(true);

let db = DB::open_cf_descriptors(&db_opts, path, vec![requires_cf_desc])?;
let db = DB::open_cf_descriptors(&db_opts, path, vec![decryptions_cf_desc])?;
Ok(RocksDBStore { db })
}

pub fn put_require(
pub fn put_decryption(
&self,
key: &[u8],
value: &[u8],
) -> Result<(), Box<dyn Error + Sync + Send>> {
self.db.put_cf(self.requires_cf_handle(), key, value)?;
self.db.put_cf(self.decryptions_cf_handle(), key, value)?;
Ok(())
}

pub fn get_require(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Box<dyn Error + Sync + Send>> {
let res = self.db.get_cf(self.requires_cf_handle(), key)?;
pub fn get_decryption(
&self,
key: &[u8],
) -> Result<Option<Vec<u8>>, Box<dyn Error + Sync + Send>> {
let res = self.db.get_cf(self.decryptions_cf_handle(), key)?;
Ok(res)
}
}

impl RocksDBStore {
const REQUIRES_CF: &'static str = "requires";
const DECRYPTIONS_CF: &'static str = "decryptions";

fn requires_cf_handle(&self) -> &ColumnFamily {
fn decryptions_cf_handle(&self) -> &ColumnFamily {
self.db
.cf_handle(Self::REQUIRES_CF)
.expect("requires CF handle")
.cf_handle(Self::DECRYPTIONS_CF)
.expect("decryptions CF handle")
}
}
Loading

0 comments on commit abcd206

Please sign in to comment.