Skip to content

Plugin API

Bernardo Ramos edited this page Aug 5, 2019 · 5 revisions

The plugins will be able to be distributed and loaded in 3 ways:

  1. Bundled in the AergoLite library (either static or dynamic), compiled using native code (C, C++ and maybe Swift, Rust or any LLVM/JIT based language). Useful for platforms in which it is not easy to use dynamic libraries and for resource constrained devices.

  2. In a separate loadable library that can be loaded using the SQLite's load_extension() SQL function:

SELECT load_extension('aergolite-mini-raft-consensus')
  1. For interpreted languages (Javascript, Python, Lua...) the application can contain the code for the plugin. In this case it must register the plugin interface before opening the database. It is done using an exported function that can be called via ctypes (Python) or node-ffi (node.js) before starting the SQLite wrapper.

Registering the plugin

The plugin must be registered in the core to be able to be used. This is done using the aergolite_plugin_register function:

SQLITE_API int aergolite_plugin_register(
  char *name,                            /* name of the plugin */
  void* (*xInit)(aergolite*, char* uri), /* initializes a new plugin instance */
  void (*xEnd)(void*),                   /* terminates the instance */
  void (*xOnNewLocalTransaction)(void*), /* on_new_local_transaction notification */
  char* (*xStatus)(void*, int extended)  /* used to retrieve the protocol status */
);

The registration must be done when the plugin is loaded, on the library's entry point.

For plugins written in interpreted languages (Python, Javascript, Lua...) the plugin code can be in the same application that uses the database. In this case the plugin registration is done before loading the SQLite wrapper.

Callbacks

The plugin must implement the functions bellow. They will be called by the AergoLite core.

Reminder: these methods on the plugin are called by the core using the main thread! (Use mutex when needed.)

-> Init

The application is opening a local database that uses this plugin. The core is requesting the plugin to create a new instance.

It must:

  • Allocate a new object to store information for this instance
  • Store the aergolite instance reference on the new allocated object
  • Create a worker thread for this instance
  • Return a pointer/handle that the core will use on the other callbacks
void* plugin_init(void* aergolite_instance, char* uri)

-> End

The application is closing a database that has an instance of this plugin. The core is requesting the plugin to terminate this instance releasing all the allocated resources, including connections and the worker thread.

void plugin_end(void* plugin_instance)

-> On_new_local_transaction

The application executed a new database transaction that was successfully committed.

The plugin method should just send a notification to its worker thread and it must return as fast as possible.

The worker thread, once received the notification, must:

  • Process its queue of local transactions that must be sent to other nodes
void on_new_local_transaction(void* plugin_instance)

-> GetStatus

The application is requesting information about the plugin state.

The plugin should return a JSON document [allocated using sqlite3_malloc ?] containing information about the connections to other peers, the consensus protocol and any useful information.

Each plugin may have different information to present as they implement different protocols.

The application makes this request via the PRAGMA protocol_status command. The core will transfer the argument to the plugin, if one is informed.

char* get_status(void* plugin_instance, char* argument)

AergoLite exported functions

These are the functions that should be used by the plugin:

aergolite_periodic

This function must be called by the plugin periodically, using a timer. The suggested interval is 500 ms.

void aergolite_periodic(aergolite *this_node)

aergolite_get_node_id

Retrieve this node's id

Each node has a unique node identifier: a 31 bit integer (no sign bit, always positive).

It is chosen randomly by the AergoLite core the first time it opens a non-initialized database.

Setting the node id by the plugin is not yet supported, but it can retrieve the value with:

int aergolite_get_node_id(aergolite *this_node);

aergolite_get_node_info

Retrieve application defined node info

This info is set by the application using the PRAGMA node_info command in the following format:

PRAGMA node_info=<string>

The information should be serialized in a single final string, using any format (JSON, netstring...) as it will be used by the application itself or its peers.

The returned memory must be released with sqlite3_free()

char* aergolite_get_node_info(aergolite *this_node);

aergolite_store_and_empty_local_db

When starting from the first time, if the local db is not empty this function can be called

not fully implemented yet

int aergolite_store_and_empty_local_db(aergolite *this_node);

aergolite_load_current_state

Load the local database, the current state agreement (last block) and verify the database integrity based on the agreed db state.

Returns the last block information in the case of success.

SQLITE_API int aergolite_load_current_state(
  aergolite *this_node,
  int64 *pblock_height,
  void **pheader,
  void **pbody,
  void **psignatures
);

aergolite_begin_state_update

Begin the process of updating the local database with data retrieved from a peer

SQLITE_API int aergolite_begin_state_update(aergolite* this_node);

aergolite_update_db_page

Update a database page with content received from a peer

SQLITE_API int aergolite_update_db_page(aergolite* this_node, uint32_t pgno, char *data, int size);

aergolite_apply_state_update

Apply the database state update with data received from the peer: the block header, the signatures and the list of modified pages.

SQLITE_API int aergolite_apply_state_update(aergolite* this_node, void *header, void *signatures, void *modified_pages);

aergolite_cancel_state_update

Cancel the process of state update

SQLITE_API void aergolite_cancel_state_update(aergolite* this_node);

aergolite_begin_state_read

Start the process of reading the local database state

SQLITE_API int aergolite_begin_state_read(aergolite* this_node);

aergolite_get_modified_pages

Retrieves the list of modified pages since height from

SQLITE_API int aergolite_get_modified_pages(aergolite* this_node, int64 from, int64 to, binn **plist);

aergolite_get_db_page

Load the database page data into memory

SQLITE_API int aergolite_get_db_page(aergolite* this_node, uint32_t pgno, void *data, int *psize);

aergolite_end_state_read

End the process of reading the local database state

SQLITE_API int aergolite_end_state_read(aergolite* this_node);

aergolite_get_node_last_nonce

Retrieve the local node's last nonce

SQLITE_API int64 aergolite_get_node_last_nonce(aergolite *this_node);

aergolite_get_local_transaction

Retrieve a transaction from the local queue.

If the nonce is set to 0, it will retrieve the first transaction that was not yet processed.

Subsequent calls can specify the nonce.

It will write the transaction nonce in the given pointer and return a binn map with transaction data.

The plugin should transfer the local transaction to its leader or peers.

int aergolite_get_local_transaction(aergolite *this_node, int64 *pnonce, binn **plog);

aergolite_get_transaction_id

Returns a transaction id based on the node id and the nonce

SQLITE_API int64 aergolite_get_transaction_id(int node_id, int64 nonce);

aergolite_execute_transaction

Used when creating or applying a new block

SQLITE_API int aergolite_execute_transaction(
  aergolite *this_node, int node_id, int64 nonce, void *list
);

aergolite_free_transaction

Release the binn map object containing the local transaction

void aergolite_free_transaction(binn *log);

aergolite_begin_block

Start the process of creating or applying a new block

SQLITE_API int aergolite_begin_block(aergolite *this_node);

aergolite_create_block

Creates a new block

SQLITE_API int aergolite_create_block(aergolite *this_node, void **pheader, void **pbody);

aergolite_apply_block

Applies a block on the local blockchain

SQLITE_API int aergolite_apply_block(aergolite *this_node, void *header, void *body, void *signatures);

aergolite_rollback_block

Cancel the process of creating or applying a new block

SQLITE_API int aergolite_rollback_block(aergolite *this_node);

aergolite_get_blockchain_status

Retrieve the blockchain status as an allocated string in JSON format

The returned memory must be released with sqlite3_free()

char * aergolite_get_blockchain_status(aergolite *this_node);

Local config

int aergolite_set_node_config_str(aergolite *this_node, char *key, char *value);
int aergolite_set_node_config_int(aergolite *this_node, char *key, int64 value);
int aergolite_set_node_config_double(aergolite *this_node, char *key, double value);
int aergolite_set_node_config_blob(aergolite *this_node, char *key, char *value, int size);
char*  aergolite_get_node_config_str(aergolite *this_node, char *key);
int64  aergolite_get_node_config_int(aergolite *this_node, char *key);
double aergolite_get_node_config_double(aergolite *this_node, char *key);
char*  aergolite_get_node_config_blob(aergolite *this_node, char *key, int *psize);

Returned memory from _str and _blob must be released with sqlite3_free()

Local queue database

int aergolite_queue_db_exec(aergolite *this_node, const char *sql, ...);
int aergolite_queue_db_query_int32(aergolite *this_node, int *pvalue, char *sql, ...);
int aergolite_queue_db_query_int64(aergolite *this_node, int64 *pvalue, char *sql, ...);
int aergolite_queue_db_query_double(aergolite *this_node, double *pvalue, char *sql, ...);
int aergolite_queue_db_query_str(aergolite *this_node, char **pvalue, char *sql, ...);
int aergolite_queue_db_query_blob(aergolite *this_node, char **pvalue, int *psize, char *sql, ...);

Returned memory from _str and _blob must be released with sqlite3_free()

Consensus database

int aergolite_consensus_db_exec(aergolite *this_node, const char *sql, ...);
int aergolite_consensus_db_query_int32(aergolite *this_node, int *pvalue, char *sql, ...);
int aergolite_consensus_db_query_int64(aergolite *this_node, int64 *pvalue, char *sql, ...);
int aergolite_consensus_db_query_double(aergolite *this_node, double *pvalue, char *sql, ...);
int aergolite_consensus_db_query_str(aergolite *this_node, char **pvalue, char *sql, ...);
int aergolite_consensus_db_query_blob(aergolite *this_node, char **pvalue, int *psize, char *sql, ...);

Returned memory from _str and _blob must be released with sqlite3_free()

Encryption

uchar* aergolite_encrypt(aergolite *this_node, uchar *data, int *psize, int counter);
uchar* aergolite_decrypt(aergolite *this_node, uchar *data, int *psize, int counter);

Log debug messages

SYNCTRACE(format, ...)

The plugin can also use the native SQLite functions. Just remind that the plugin is running on a separate thread from the application.

Some useful are:

  • sqlite3_malloc
  • sqlite3_free
void *sqlite3_malloc_zero(int64 n);
char *sqlite3_memdup(char *source, int size);
char *sqlite3_strdup(char *text);
char *stripchr(char *mainstr, int separator);

Clone this wiki locally