State Management
Current State#
Current state of immudb provides proof that clients can use to verify immudb:
Go
1package main
2
3import (
4 "context"
5 "fmt"
6 "log"
7
8 immudb "github.com/codenotary/immudb/pkg/client"
9)
10
11func main() {
12 opts := immudb.DefaultOptions().
13 WithAddress("localhost").
14 WithPort(3322)
15
16 client := immudb.NewClient().WithOptions(opts)
17 err := client.OpenSession(
18 context.TODO(),
19 []byte(`immudb`),
20 []byte(`immudb`),
21 "defaultdb",
22 )
23 if err != nil {
24 log.Fatal(err)
25 }
26
27 defer client.CloseSession(context.TODO())
28
29 state, err := client.CurrentState(context.TODO())
30 if err != nil {
31 log.Fatal(err)
32 }
33
34 fmt.Printf("current state is: %+v\n", state)
35}Python
1from immudb import ImmudbClient
2
3URL = "localhost:3322" # immudb running on your machine
4LOGIN = "immudb" # Default username
5PASSWORD = "immudb" # Default password
6DB = b"defaultdb" # Default database name (must be in bytes)
7
8def main():
9 client = ImmudbClient(URL)
10 client.login(LOGIN, PASSWORD, database = DB)
11
12 state = client.currentState() # immudb.rootService.State
13 print(state.db) # Current selected DB
14 print(state.txId) # Current transaction ID
15 print(state.txHash) # Current transaction hash
16 print(state.signature) # Current signature
17
18if __name__ == "__main__":
19 main()Java
1ImmuState currState = immuClient.currentState();
2
3System.out.printf("The current state is " + currState.toString());.NET
1var client = new ImmuClient();
2await client.Open("immudb", "immudb", "defaultdb");
3
4var state = client.State;
5System.Console.WriteLine($"The current state is: {state}");
6
7await client.Close();Node.js
1import ImmudbClient from 'immudb-node'
2
3const IMMUDB_HOST = '127.0.0.1'
4const IMMUDB_PORT = '3322'
5const IMMUDB_USER = 'immudb'
6const IMMUDB_PWD = 'immudb'
7
8const cl = new ImmudbClient({ host: IMMUDB_HOST, port: IMMUDB_PORT });
9
10(async () => {
11 await cl.login({ user: IMMUDB_USER, password: IMMUDB_PWD })
12
13 const currentStateRes = await cl.currentState()
14 console.log('success: currentState', currentStateRes)
15})()Others
If you’re using another development language, please refer to the immugw option.
Automated verification of state by immudb SDK#
It’s the responsibility of the immudb client to track the server state. That way it can check each verified read or write operation against a trusted state.
Go
The component in charge of state handling is the StateService.
To set up the stateService 3 interfaces need to be implemented and provided to the StateService constructor:
Cacheinterface in thecachepackage. Standard cache.NewFileCache provides a file state store solution.StateProviderin thestateServicepackage. It provides a fresh state from immudb server when the client is being initialized for the first time. Standard StateProvider provides a service that retrieve immudb first state hash from a gRPC endpoint.UUIDProviderin thestateServicepackage. It provides the immudb identifier. This is needed to allow the client to safely connect to multiple immudb instances. Standard UUIDProvider provides the immudb server identifier from a gRPC endpoint.
Following an example how to obtain a client instance with a custom state service.
1func MyCustomImmuClient(options *c.Options) (cli c.ImmuClient, err error) {
2 ctx := context.Background()
3
4 cli = c.DefaultClient()
5
6 options.DialOptions = cli.SetupDialOptions(options)
7
8 cli.WithOptions(options)
9
10 var clientConn *grpc.ClientConn
11 if clientConn, err = cli.Connect(ctx); err != nil {
12 return nil, err
13 }
14
15 cli.WithClientConn(clientConn)
16
17 serviceClient := schema.NewImmuServiceClient(clientConn)
18 cli.WithServiceClient(serviceClient)
19
20 if err = cli.WaitForHealthCheck(ctx); err != nil {
21 return nil, err
22 }
23
24 immudbStateProvider := stateService.NewImmudbStateProvider(serviceClient)
25 immudbUUIDProvider := stateService.NewImmudbUUIDProvider(serviceClient)
26
27 customDir := "custom_state_dir"
28 os.Mkdir(customDir, os.ModePerm)
29 stateService, err := stateService.NewStateService(
30 cache.NewFileCache(customDir),
31 logger.NewSimpleLogger("immuclient", os.Stderr),
32 immudbStateProvider,
33 immudbUUIDProvider)
34 if err != nil {
35 return nil, err
36 }
37
38 dt, err := timestamp.NewDefaultTimestamp()
39 if err != nil {
40 return nil, err
41 }
42
43 ts := c.NewTimestampService(dt)
44 cli.WithTimestampService(ts).WithStateService(stateService)
45
46 return cli, nil
47}Python
1from immudb import ImmudbClient
2from immudb.client import PersistentRootService
3
4# By default RootService is writing state to RAM
5# You can choose different implementation of RootService
6
7# Persistent root service will save to the disk after every verified transaction
8
9URL = "localhost:3322" # immudb running on your machine
10LOGIN = "immudb" # Default username
11PASSWORD = "immudb" # Default password
12DB = b"defaultdb" # Default database name (must be in bytes)
13PERSISTENT_ROOT_SERVICE_PATH = "/tmp/psr.db"
14
15def main():
16 client = ImmudbClient(URL, rs = PersistentRootService(PERSISTENT_ROOT_SERVICE_PATH))
17 client.login(LOGIN, PASSWORD, database = DB)
18 client.verifiedSet(b'x', b'1')
19 client.verifiedGet(b'x')
20 client.verifiedSet(b'x', b'2')
21 client.verifiedGet(b'x')
22
23if __name__ == "__main__":
24 main()Java
Any immudb server has its own UUID. This is exposed as part of the login response.
Java SDK can use any implementation of the ImmuStateHolder interface, which specifies two methods:
ImmuState getState(String serverUuid, String database)for getting a state.void setState(String serverUuid, ImmuState state)for setting a state.
Note that a state is related to a specific database (identified by its name) and a server (identified by the UUID). Currently, Java SDK offers two implementations of this interface for storing and retriving a state:
FileImmuStateHolderthat uses a disk file based store.SerializableImmuStateHolderthat uses an in-memory store.
As most of the code snippets include FileImmuStateHolder, please find below an example using the in-memory alternative:
1SerializableImmuStateHolder stateHolder = new SerializableImmuStateHolder();
2
3ImmuClient immuClient = ImmuClient.newBuilder()
4 .withStateHolder(stateHolder)
5 .withServerUrl("localhost")
6 .withServerPort(3322)
7 .build();
8
9immuClient.login("immudb", "immudb");
10immuClient.useDatabase("defaultdb");
11// ...
12immuClient.logout();.NET
Any immudb server has its own UUID. This is exposed as part of the login response.
.NET SDK can use any implementation of the ImmuStateHolder interface, which specifies two methods:
ImmuState GetState(Sstring serverUuid, string database)for getting a state.void SetState(string serverUuid, ImmuState state)for setting a state.
Note that a state is related to a specific database (identified by its name) and a server (identified by the UUID).
Currently, .NET SDK offers one implementations of this interface for storing and retriving a state, FileImmuStateHolder,
that uses a disk file based store.
As most of the code snippets include FileImmuStateHolder, please find below an example using the in-memory alternative:
1 FileImmuStateHolder stateHolder = FileImmuStateHolder.NewBuilder()
2 .WithStatesFolder("./my_immuapp_states")
3 .Build();
4
5ImmuClient immuClient = ImmuClient.NewBuilder()
6 .WithStateHolder(stateHolder)
7 .Build();
8
9await client.Open("immudb", "immudb", "defaultdb");
10await client.Close();Node.js
This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on Node.js sdk github project
Others
If you’re using another development language, please refer to the immugw option.
Verify state signature#
If immudb is launched with a private signing key, each signed request can be verified with the public key.
This ensures the server identity.
To run immudb server with state signature use:
1./immudb --signingKey test/signer/ec1.keyTo generate an elliptic curve private key use:
1openssl ecparam -name prime256v1 -genkey -noout -out private.keyTo generate the public key from the previous one:
1openssl ec -in private.key -pubout -out public.keyGo
1package main
2
3import (
4 "context"
5 "fmt"
6 "log"
7
8 immudb "github.com/codenotary/immudb/pkg/client"
9)
10
11func main() {
12 opts := immudb.DefaultOptions().
13 WithAddress("localhost").
14 WithServerSigningPubKey("public.key").
15 WithPort(3322)
16
17 client := immudb.NewClient().WithOptions(opts)
18 err := client.OpenSession(
19 context.TODO(),
20 []byte(`immudb`),
21 []byte(`immudb`),
22 "defaultdb",
23 )
24 if err != nil {
25 log.Fatal(err)
26 }
27
28 defer client.CloseSession(context.TODO())
29
30 _, err = client.Login(
31 context.TODO(),
32 []byte(`immudb`),
33 []byte(`immudb`),
34 )
35 if err != nil {
36 log.Fatal(err)
37 }
38
39 _, err = client.Set(
40 context.TODO(),
41 []byte(`immudb`),
42 []byte(`hello world`),
43 )
44 if err != nil {
45 log.Fatal(err)
46 }
47
48 state, err := client.CurrentState(context.TODO())
49 if err != nil {
50 // if signature is not verified here is trigger an appropriate error
51 log.Fatal(err)
52 }
53
54 fmt.Print(state)
55}Python
1from immudb import ImmudbClient
2
3# All operations are checked against public/private key pair
4
5URL = "localhost:3322" # immudb running on your machine
6LOGIN = "immudb" # Default username
7PASSWORD = "immudb" # Default password
8DB = b"defaultdb" # Default database name (must be in bytes)
9KEYFILE = "public_signing_key.pem" # Public key path
10 # needs immudb server with --signingKey option enabled
11 # pointing to corresponding private key
12
13def main():
14 client = ImmudbClient(URL, publicKeyFile = KEYFILE)
15 client.login(LOGIN, PASSWORD, database = DB)
16 client.set(b'x', b'1')
17 client.verifiedGet(b'x') # This operation will also fail if public key
18 # is not paired with private one used in immudb
19
20 state = client.currentState() # immudb.rootService.State
21 print(state.db) # Current selected DB
22 print(state.txId) # Current transaction ID
23 print(state.txHash) # Current transaction hash
24 print(state.signature) # Current signature
25
26if __name__ == "__main__":
27 main()Java
1// Having immudb server running with state signature enabled
2// (by starting it, for example using `immudb --signingKey private_key.pem`)
3// we provision the client with the public key file, and this implies that
4// state signature verification is done on the client side
5// each time the state is retrieved from the server.
6
7File publicKeyFile = new File("path/to/public_key.pem");
8
9immuClient = ImmuClient.newBuilder()
10 .withServerUrl("localhost")
11 .withServerPort(3322)
12 .withServerSigningKey(publicKeyFile.getAbsolutePath())
13 .build();
14
15try {
16 ImmuState state = immuClient.currentState();
17 // It should all be ok as long as the immudb server has been started with
18 // state signature feature enabled, otherwise, this verification will fail.
19
20} catch (RuntimeException e) {
21 // State signature failed.
22}.NET
1// Having immudb server running with state signature enabled
2// (by starting it, for example using `immudb --signingKey private_key.pem`)
3// we provision the client with the public key file, and this implies that
4// state signature verification is done on the client side
5// each time the state is retrieved from the server.
6
7Assembly asm = Assembly.GetExecutingAssembly();
8string resourceName = "public_key.pem";
9AsymmetricKeyParameter assymKey;
10ImmuClient client;
11try
12{
13 using (Stream? stream = asm.GetManifestResourceStream(resourceName))
14 {
15 if (stream == null)
16 {
17 Assert.Fail("Could not read resource");
18 }
19 using (TextReader tr = new StreamReader(stream))
20 {
21 PemReader pemReader = new PemReader(tr);
22 assymKey = (AsymmetricKeyParameter)pemReader.ReadObject();
23 }
24 }
25 client = ImmuClient.NewBuilder()
26 .WithServerUrl("localhost")
27 .WithServerSigningKey(assymKey)
28 .Build();
29}
30catch (Exception e)
31{
32 Console.WriteLine($"An exception occurred: {e}");
33 return;
34}Node.js
1import ImmudbClient from 'immudb-node'
2
3const IMMUDB_HOST = '127.0.0.1'
4const IMMUDB_PORT = '3322'
5const IMMUDB_USER = 'immudb'
6const IMMUDB_PWD = 'immudb'
7
8const cl = new ImmudbClient({ host: IMMUDB_HOST, port: IMMUDB_PORT });
9
10(async () => {
11 await cl.login({ user: IMMUDB_USER, password: IMMUDB_PWD })
12 await cl.set({ key: 'immudb', value: 'hello world' })
13
14 const currentStateRes = await cl.currentState();
15 console.log('success: currentState', currentStateRes)
16})()Others
If you’re using another development language, please refer to the immugw option.