State management

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.

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:

  • Cache interface in the cache package. Standard cache.NewFileCache provides a file state store solution.
  • StateProvider in the stateService package. 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.
  • UUIDProvider in the stateService package. 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.

  1. func MyCustomImmuClient(options *c.Options) (cli c.ImmuClient, err error) {
  2. ctx := context.Background()
  3. cli = c.DefaultClient()
  4. options.DialOptions = cli.SetupDialOptions(options)
  5. cli.WithOptions(options)
  6. var clientConn *grpc.ClientConn
  7. if clientConn, err = cli.Connect(ctx); err != nil {
  8. return nil, err
  9. }
  10. cli.WithClientConn(clientConn)
  11. serviceClient := schema.NewImmuServiceClient(clientConn)
  12. cli.WithServiceClient(serviceClient)
  13. if err = cli.WaitForHealthCheck(ctx); err != nil {
  14. return nil, err
  15. }
  16. immudbStateProvider := stateService.NewImmudbStateProvider(serviceClient)
  17. immudbUUIDProvider := stateService.NewImmudbUUIDProvider(serviceClient)
  18. customDir := "custom_state_dir"
  19. os.Mkdir(customDir, os.ModePerm)
  20. stateService, err := stateService.NewStateService(
  21. cache.NewFileCache(customDir),
  22. logger.NewSimpleLogger("immuclient", os.Stderr),
  23. immudbStateProvider,
  24. immudbUUIDProvider)
  25. if err != nil {
  26. return nil, err
  27. }
  28. dt, err := timestamp.NewDefaultTimestamp()
  29. if err != nil {
  30. return nil, err
  31. }
  32. ts := c.NewTimestampService(dt)
  33. cli.WithTimestampService(ts).WithStateService(stateService)
  34. return cli, nil
  35. }

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:

  • FileImmuStateHolder that uses a disk file based store.
  • SerializableImmuStateHolder that uses an in-memory store.

As most of the code snippets include FileImmuStateHolder, please find below an example using the in-memory alternative:

  1. SerializableImmuStateHolder stateHolder = new SerializableImmuStateHolder();
  2. ImmuClient immuClient = ImmuClient.newBuilder()
  3. .withStateHolder(stateHolder)
  4. .withServerUrl("localhost")
  5. .withServerPort(3322)
  6. .build();
  7. immuClient.login("immudb", "immudb");
  8. immuClient.useDatabase("defaultdb");
  9. // ...
  10. immuClient.logout();

This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on Python sdk github projectState management - 图1 (opens new window)

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 projectState management - 图2 (opens new window)

This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on .Net sdk github projectState management - 图3 (opens new window)

If you’re using another development language, please read up on our immugwState management - 图4 (opens new window) option.

Verify state signature

If immudb is launched with a private signing key, each signed request can be verified with the public key. In this way the identity of the server can be proven. Check state signature to see how to generate a valid key.

  1. c, err := client.NewImmuClient(client.DefaultOptions().WithServerSigningPubKey("../../immudb/src/wrong.public.key"))
  2. if err != nil {
  3. log.Fatal(err)
  4. }
  5. ctx := context.Background()
  6. lr , err := c.Login(ctx, []byte(`immudb`), []byte(`immudb`))
  7. if err != nil {
  8. log.Fatal(err)
  9. }
  10. md := metadata.Pairs("authorization", lr.Token)
  11. ctx = metadata.NewOutgoingContext(context.Background(), md)
  12. if _, err := c.Set(ctx, []byte(`immudb`), []byte(`hello world`)); err != nil {
  13. log.Fatal(err)
  14. }
  15. var state *schema.ImmutableState
  16. if state, err = c.CurrentState(ctx); err != nil {
  17. log.Fatal(err) // if signature is not verified here is trigger an appropriate error
  18. }
  19. fmt.Print(state)
  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. File publicKeyFile = new File("path/to/public_key.pem");
  7. immuClient = ImmuClient.newBuilder()
  8. .withServerUrl("localhost")
  9. .withServerPort(3322)
  10. .withServerSigningKey(publicKeyFile.getAbsolutePath())
  11. .build();
  12. try {
  13. ImmuState state = immuClient.currentState();
  14. // It should all be ok as long as the immudb server has been started with
  15. // state signature feature enabled, otherwise, this verification will fail.
  16. } catch (RuntimeException e) {
  17. // State signature failed.
  18. }

This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on Python sdk github projectState management - 图5 (opens new window)

  1. import ImmudbClient from 'immudb-node'
  2. const IMMUDB_HOST = '127.0.0.1'
  3. const IMMUDB_PORT = '3322'
  4. const IMMUDB_USER = 'immudb'
  5. const IMMUDB_PWD = 'immudb'
  6. const cl = new ImmudbClient({ host: IMMUDB_HOST, port: IMMUDB_PORT });
  7. (async () => {
  8. await cl.login({ user: IMMUDB_USER, password: IMMUDB_PWD })
  9. await cl.set({ key: 'immudb', value: 'hello world' })
  10. const currentStateRes = await cl.currentState();
  11. console.log('success: currentState', currentStateRes)
  12. })()

This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on .Net sdk github projectState management - 图6 (opens new window)

If you’re using another development language, please read up on our immugwState management - 图7 (opens new window) option.

Tamperproof reading and writing

You can read and write records securely using a built-in cryptographic verification.

Verified get and set

The client implements the mathematical validations, while your application uses a traditional read or write function.

  1. tx, err := client.VerifiedSet(ctx, []byte(`hello`), []byte(`immutable world`))
  2. if err != nil {
  3. log.Fatal(err)
  4. }
  5. fmt.Printf("Successfully committed and verified tx %d\n", tx.Id)
  6. entry, err := client.VerifiedGet(ctx, []byte(`hello`))
  7. if err != nil {
  8. log.Fatal(err)
  9. }
  10. fmt.Printf("Successfully retrieved and verified entry: %v\n", entry)
  1. try {
  2. TxMetadata txMd = immuClient.verifiedSet(key, val);
  3. System.out.println("Successfully committed and verified tx " + txMd.id);
  4. } catch (VerificationException e) {
  5. // ...
  6. }
  7. try {
  8. Entry vEntry = immuClient.verifiedGet(key);
  9. System.out.println("Successfully retrieved and verified entry: " + vEntry);
  10. } catch (VerificationException e) {
  11. // ...
  12. }

This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on Python sdk github projectState management - 图8 (opens new window)

  1. import ImmudbClient from 'immudb-node'
  2. import Parameters from 'immudb-node/types/parameters'
  3. const IMMUDB_HOST = '127.0.0.1'
  4. const IMMUDB_PORT = '3322'
  5. const IMMUDB_USER = 'immudb'
  6. const IMMUDB_PWD = 'immudb'
  7. const cl = new ImmudbClient({ host: IMMUDB_HOST, port: IMMUDB_PORT });
  8. (async () => {
  9. await cl.login({ user: IMMUDB_USER, password: IMMUDB_PWD })
  10. const verifiedSetReq: Parameters.VerifiedSet = {
  11. key: 'hello',
  12. value: 'world',
  13. }
  14. const verifiedSetRes = await cl.verifiedSet(verifiedSetReq)
  15. console.log('success: verifiedSet', verifiedSetRes)
  16. const verifiedGetReq: Parameters.VerifiedGet = {
  17. key: 'hello',
  18. }
  19. const verifiedGetRes = await cl.verifiedGet(verifiedGetReq)
  20. console.log('success: verifiedGet', verifiedGetRes)
  21. })()

This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on .Net sdk github projectState management - 图9 (opens new window)

If you’re using another development language, please read up on our immugwState management - 图10 (opens new window) option.