
#include "db_functions.c"
#include "secp256k1-vrf.h"

#include "../common/sha256.c"

#define majority(X)  X / 2 + 1

#define USE_ADD_NODE       11
#define USE_NODE_TYPE      12
#define USE_NODE_TYPE_ALL  13

#ifdef __APPLE__
#define BLOCK_INTERVAL   1000
#elif defined(_WIN32)
#define BLOCK_INTERVAL   5000
#else
#define BLOCK_INTERVAL   3000
#endif

#ifdef __APPLE__
const int wait_time = 150;
#else
const int wait_time = 250;
#endif

const int wait_repeat = 200;

const int num_system_tables = 2;

/*
** Private and public keys for the blockchain admin
*/

unsigned char privkey[32], pubkey[36];
secp256k1_pubkey pubkey_obj;
secp256k1_context *ecdsa_ctx;
size_t pklen;
char pkhex[76];
char pkstr[56];

/****************************************************************************/

/*
** Generate private and public keys to manage the network
*/
void prepare_blockchain_admin_keys(){
  int rc;

  /* generate a private key for the admin */
  sqlite3_randomness(32, privkey);

  /* create an ECDSA context for signing and verification */
  ecdsa_ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);

  /* get the public key from the private one */
  rc = secp256k1_ec_pubkey_create(ecdsa_ctx, &pubkey_obj, privkey);
  assert( rc==1 );
  pklen = 33;
  rc = secp256k1_ec_pubkey_serialize(ecdsa_ctx, pubkey, &pklen,
                                     &pubkey_obj, SECP256K1_EC_COMPRESSED);
  assert( rc==1 );

  /* get the hexadecimal representation of the public key */
  to_hex((char*)pubkey, pklen, pkhex);
  /* get the account representation of the public key */
  pubkey_to_address(pubkey, pkstr, sizeof pkstr);

}

/****************************************************************************/

/*
** Sign transactions generated by the blockchain admin
*/

/*
def on_sign_transaction(data):
  print "txn to be signed: " + data
  signature = sign(data, privkey)
  return hex(pubkey) + ":" + hex(signature)
*/

static void on_sign_transaction(sqlite3_context *context, int argc, sqlite3_value **argv){
  secp256k1_ecdsa_signature sig;
  char *data, result[256], sighex[160];
  unsigned char hash[32], signature[76];
  size_t siglen;

  data = (char*) sqlite3_value_text(argv[0]);

  printf("transaction to sign: [%s]\n", data);

  sha256(hash, data, strlen(data));

  assert(secp256k1_ecdsa_sign(ecdsa_ctx, &sig, hash, privkey, NULL, NULL)==1);
  assert(secp256k1_ecdsa_signature_serialize_compact(ecdsa_ctx, signature, &sig)==1);

  siglen = 64;  /* size of the compact signature format */

  to_hex((char*)signature, siglen, sighex);
  sprintf(result, "%s:%s", pkhex, sighex);
  sqlite3_result_text(context, result, -1, SQLITE_TRANSIENT);
}

/****************************************************************************/
/****************************************************************************/

void check_limit_of_open_files() {
  char *cmd = "ulimit -Sn";
  char buf[64];
  FILE *fp;

  /* Open the command for reading. */
  if ((fp = popen(cmd, "r")) == NULL) {
    printf("Failed to run command\n" );
    exit(1);
  }

  /* Read the output */
  if (fgets(buf, sizeof(buf), fp) != NULL) {
    if( atoi(buf)<4096 ){
      puts("\nrun the command bellow to increase the limit of open files:\n\n"
           "    ulimit -Sn 4096\n");
      pclose(fp);
      exit(1);
    }
  }

  /* close */
  pclose(fp);
}

/****************************************************************************/

void delete_files(int n){
  int i;
  for(i=1; i<=n; i++){
    unlinkf("db%d.db", i);
    unlinkf("db%d.db-local", i);
    unlinkf("db%d.db-consensus", i);
    unlinkf("db%d.db-rotation", i);
    unlinkf("db%d.db-shm", i);
    unlinkf("db%d.db-state", i);
    unlinkf("db%d.db-state-wal", i);
    unlinkf("db%d.db-state-shm", i);
  }
}

/****************************************************************************/

void delete_log(){
  char fname[256];
  sprintf(fname, "log-%d", getpid());
  unlink(fname);
}

/****************************************************************************/

void errorLogCallback(void *pArg, int iErrCode, const char *zMsg){
  fprintf(stderr, "(%d) %s\n", iErrCode, zMsg);
}

/****************************************************************************/

void print_nodes(char *title, int list[]){
  printf("%s=%d  { ", title, len_array_list(list));
  for(int i=0; list[i]; i++){
    if( i>0 ) printf(", ");
    printf("%d", list[i]);
  }
  puts(" }");
}

/****************************************************************************/
/****************************************************************************/

void test_5_nodes(int bind_to_random_ports){
  sqlite3 *db1, *db2, *db3, *db4, *db5;
  int rc, count, done;

  printf("test_5_nodes(random_ports=%d)...", bind_to_random_ports); fflush(stdout);

  /* delete the db files if they exist */

  delete_files(5);
  delete_log();

  /* open the connections to the databases */

  assert( sqlite3_open("file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302", &db1)==SQLITE_OK );
  assert( sqlite3_open("file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301", &db2)==SQLITE_OK );

  if( bind_to_random_ports ){
  assert( sqlite3_open("file:db3.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302", &db3)==SQLITE_OK );
  assert( sqlite3_open("file:db4.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302", &db4)==SQLITE_OK );
  assert( sqlite3_open("file:db5.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302", &db5)==SQLITE_OK );
  }else{
  assert( sqlite3_open("file:db3.db?blockchain=on&bind=4303&discovery=127.0.0.1:4301,127.0.0.1:4302", &db3)==SQLITE_OK );
  assert( sqlite3_open("file:db4.db?blockchain=on&bind=4304&discovery=127.0.0.1:4301,127.0.0.1:4302", &db4)==SQLITE_OK );
  assert( sqlite3_open("file:db5.db?blockchain=on&bind=4305&discovery=127.0.0.1:4301,127.0.0.1:4302", &db5)==SQLITE_OK );
  }


  /* execute 3 db transactions on one of the databases */

  db_check_int(db1, "PRAGMA last_nonce", 1);

  db_execute(db1, "create table t1 (name)");
  db_execute(db1, "insert into t1 values ('aa1')");
  db_execute(db1, "insert into t1 values ('aa2')");

  db_check_int(db1, "PRAGMA last_nonce", 4);
  db_check_int(db2, "PRAGMA last_nonce", 1);
  db_check_int(db3, "PRAGMA last_nonce", 1);


  /* wait until the transactions are processed in a new block */

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    char *result;
    sleep_ms(wait_time);
    rc = db_query_str(&result, db1, "PRAGMA transaction_status(4)");
    assert(rc==SQLITE_OK);
    done = (strcmp(result,"processed")==0);
    sqlite3_free(result);
    printf("."); fflush(stdout);
  }
  assert(done);

  printf("1"); fflush(stdout);


  /* db2 */

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db2, "select count(*) from sqlite_master where name='t1'");
    assert(rc==SQLITE_OK);
    printf("."); fflush(stdout);
    done = (result>0);
  }
  assert(done);

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db2, "select count(*) from t1");
    assert(rc==SQLITE_OK);
    printf("."); fflush(stdout);
    done = (result>1);
  }
  assert(done);

  db_check_int(db2, "select count(*) from t1", 2);
  db_check_int(db2, "select count(*) from t1 where name='aa1'", 1);
  db_check_int(db2, "select count(*) from t1 where name='aa2'", 1);

  printf("2"); fflush(stdout);


  /* db3 */

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db3, "select count(*) from sqlite_master where name='t1'");
    assert(rc==SQLITE_OK);
    printf("."); fflush(stdout);
    done = (result>0);
  }
  assert(done);

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db3, "select count(*) from t1");
    assert(rc==SQLITE_OK);
    printf("."); fflush(stdout);
    done = (result>1);
  }
  assert(done);

  db_check_int(db3, "select count(*) from t1", 2);
  db_check_int(db3, "select count(*) from t1 where name='aa1'", 1);
  db_check_int(db3, "select count(*) from t1 where name='aa2'", 1);

  printf("3"); fflush(stdout);


  /* db4 */

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db4, "select count(*) from sqlite_master where name='t1'");
    assert(rc==SQLITE_OK);
    printf("."); fflush(stdout);
    done = (result>0);
  }
  assert(done);

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db4, "select count(*) from t1");
    assert(rc==SQLITE_OK);
    printf("."); fflush(stdout);
    done = (result>1);
  }
  assert(done);

  db_check_int(db4, "select count(*) from t1", 2);
  db_check_int(db4, "select count(*) from t1 where name='aa1'", 1);
  db_check_int(db4, "select count(*) from t1 where name='aa2'", 1);

  printf("4"); fflush(stdout);


  /* db5 */

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db5, "select count(*) from sqlite_master where name='t1'");
    assert(rc==SQLITE_OK);
    printf("."); fflush(stdout);
    done = (result>0);
  }
  assert(done);

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db5, "select count(*) from t1");
    assert(rc==SQLITE_OK);
    printf("."); fflush(stdout);
    done = (result>1);
  }
  assert(done);

  db_check_int(db5, "select count(*) from t1", 2);
  db_check_int(db5, "select count(*) from t1 where name='aa1'", 1);
  db_check_int(db5, "select count(*) from t1 where name='aa2'", 1);

  printf("5"); fflush(stdout);


  /* db1 */

  db_check_int(db1, "select count(*) from t1", 2);
  db_check_int(db1, "select count(*) from t1 where name='aa1'", 1);
  db_check_int(db1, "select count(*) from t1 where name='aa2'", 1);


  sqlite3_close(db1);
  sqlite3_close(db2);
  sqlite3_close(db3);
  sqlite3_close(db4);
  sqlite3_close(db5);

  puts(" done");

}

/****************************************************************************/

/*
** Try to add nodes to the network using an account that is not the blockchain admin
*/
void test_invalid_admin(int n, int add_from_node, bool bind_to_random_ports, int block_interval){
  sqlite3 *db[512];
  char uri[256];
  char node_pubkey[512][72];
  int last_nonce[512];
  sqlite3_stmt *stmt=NULL;
  int rc, i, node, count, done;

  unsigned char admin_privkey[32], admin_pubkey[36];
  unsigned char user_privkey[32], user_pubkey[36];
  size_t user_pklen;
  char admin_pkstr[56], user_pkstr[56];

  printf("test_invalid_admin(nodes=%d add_from_node=%d random_ports=%s block_interval=%d)...",
         n, add_from_node, bind_to_random_ports ? "yes" : "no", block_interval); fflush(stdout);

  assert(n>2 && n<512);

  /* delete the db files if they exist */
  delete_files(n);
  delete_log();

  /* generate private and public keys to manage the network */
  prepare_blockchain_admin_keys();

  /* open the connections to the databases using the admin public key */

  sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&admin=%s&password=test&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[1])==SQLITE_OK );

  sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&admin=%s&password=test&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[2])==SQLITE_OK );

  for(i=3; i<=n; i++){
    if( bind_to_random_ports ){
      sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&admin=%s&password=test&block_interval=%d", i, pkstr, block_interval);
    }else{
      sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&admin=%s&password=test&block_interval=%d", i, 4300 + i, pkstr, block_interval);
    }
    //puts(uri); fflush(stdout);
    assert( sqlite3_open(uri, &db[i])==SQLITE_OK );
  }


  /* set the initial nonce value for each node */

  for(i=1; i<=n; i++){
    last_nonce[i] = 0;
  }

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* check the list of nodes connected to each of them */

  for(i=1; i<=n; i++){
    int nrows;
loc_again1:
    rc = sqlite3_prepare_v2(db[i], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );

    // node_id | pubkey | type | address | CPU | OS | hostname | app | node_info |

    assert( sqlite3_column_count(stmt)==9 );
    nrows = 0;

    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *type = (char*)sqlite3_column_text(stmt, 2);
      assert( type && strcmp(type,"unauthorized")==0 );

      /* count how many peers this node is connected to */
      nrows++;

      /* the first item has info about the requester node */
      if( nrows==1 ){
        char *p = (char*)sqlite3_column_text(stmt, 1);
        assert( p && strlen(p)<sizeof(node_pubkey[1]) );
        strcpy(node_pubkey[i], p);
      }
    }
    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;

    if( (i<=2 && nrows<n) ||
        (i>2  && nrows<3) ){
      printf("."); fflush(stdout);
      sleep_ms(wait_time);
      goto loc_again1;
    }
  }

  puts("");


  /* register the callback function used to sign the admin transactions */

  int exec_from_node = 3;

  i = add_from_node;
  sqlite3_create_function(db[i], "sign_transaction", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
      NULL, &on_sign_transaction, NULL, NULL);

  i = exec_from_node;
  sqlite3_create_function(db[i], "sign_transaction", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
      NULL, &on_sign_transaction, NULL, NULL);


  /* store the admin private and public keys */
  memcpy(admin_privkey, privkey, 32);
  strcpy(admin_pkstr, pkstr);

  /* prepare fake admin private and public keys */
  sqlite3_randomness(32, user_privkey);
  rc = secp256k1_ec_pubkey_create(ecdsa_ctx, &pubkey_obj, user_privkey);
  assert( rc==1 );
  user_pklen = 33;
  rc = secp256k1_ec_pubkey_serialize(ecdsa_ctx, user_pubkey, &user_pklen,
                                     &pubkey_obj, SECP256K1_EC_COMPRESSED);
  assert( rc==1 );
  //to_hex((char*)user_pubkey, user_pklen, user_pkhex);
  pubkey_to_address(user_pubkey, user_pkstr, sizeof user_pkstr);


  /* add nodes to the network.
  ** the command is signed in the callback function, using the network admin's private key */

  /* include some nodes on the network */
  for(node=1; node<=n; node++){
    char cmd[128];
    sprintf(cmd, "pragma add_node='%s'", node_pubkey[node]);
    if( node%2==0 ){
      printf("adding node %d to the network - not admin\n", node); fflush(stdout);
      memcpy(privkey, user_privkey, 32);
      strcpy(pkstr, user_pkstr);
      db_catch(db[add_from_node], cmd);
    }else{
      printf("adding node %d to the network - from admin\n", node); fflush(stdout);
      memcpy(privkey, admin_privkey, 32);
      strcpy(pkstr, admin_pkstr);
      db_execute(db[add_from_node], cmd);
      sleep_ms(block_interval);
    }
  }

  /* restore the blockchain admin keys */
  memcpy(privkey, admin_privkey, 32);
  strcpy(pkstr, admin_pkstr);

  /* ensure that the nodes are included on the blockchain network */

  for(node=1; node<=n; node++){
    int nrows;
    printf("checking node %d\n", node); fflush(stdout);
loc_again2:
    sqlite3_finalize(stmt); stmt = NULL;
    rc = sqlite3_prepare_v2(db[node], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );

    // node_id | pubkey | type | address | CPU | OS | hostname | app | node_info |

    assert( sqlite3_column_count(stmt)==9 );

    nrows = 0;
    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      /* count how many peers this node is connected to */
      nrows++;
    }

    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;

    printf("connected to %d nodes\n", nrows); fflush(stdout);
    if( node<=2 ){
      assert( nrows==n );
    }else if( node%2==0 ){
      assert( nrows==3 );  /* itself + 2 known nodes */
    }else{
      if( nrows<n/2 ) goto loc_again2;
    }
  }



  last_nonce[add_from_node] = n / 2;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* execute 3 db transactions on one of the databases */

  printf("executing transactions on nodes..."); fflush(stdout);

  db_execute(db[exec_from_node], "create table t1 (name)");
  db_execute(db[exec_from_node], "insert into t1 values ('aa1')");
  db_execute(db[exec_from_node], "insert into t1 values ('aa2')");

  last_nonce[exec_from_node] += 3;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* wait until the transactions are processed in a new block */

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    char *result;
    sleep_ms(wait_time);
    rc = db_query_str(&result, db[exec_from_node], "PRAGMA transaction_status(3)");
    assert(rc==SQLITE_OK);
    done = (strcmp(result,"processed")==0);
    sqlite3_free(result);
    printf("."); fflush(stdout);
  }
  assert(done);

  puts("");


  /* check if the data was replicated to the other nodes */

  for(i=1; i<=n; i++){
    if( i%2==0 ) continue;

    printf("checking node %d\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where name='t1'");
      assert(rc==SQLITE_OK);
      done = (result>0);
    }
    assert(done);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result>1);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1", 2);
    db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);

  }


  /* ensure that the data was NOT replicated to the non-authorized nodes */

  for(i=2; i<=n; i++){
    if( i%2==1 ) continue;
    printf("checking node %d\n", i); fflush(stdout);
    db_check_int(db[i], "select count(*) from sqlite_master", 0);
  }


  /* test non-allowed commands */

  printf("testing non-allowed commands..."); fflush(stdout);

  db_catch(db[add_from_node], "insert into aergolite_nodes (node_id,pubkey) values (111,'test')");
  db_catch(db[add_from_node], "update aergolite_nodes set last_nonce = 0");
  db_catch(db[add_from_node], "delete from aergolite_nodes");
  db_catch(db[add_from_node], "drop table aergolite_nodes");
  db_catch(db[add_from_node], "alter table aergolite_nodes add column test");
  db_catch(db[add_from_node], "alter table aergolite_nodes rename to new_name");

  db_catch(db[exec_from_node], "insert into aergolite_nodes (node_id,pubkey) values (111,'test')");
  db_catch(db[exec_from_node], "update aergolite_nodes set last_nonce = 0");
  db_catch(db[exec_from_node], "delete from aergolite_nodes");
  db_catch(db[exec_from_node], "drop table aergolite_nodes");
  db_catch(db[exec_from_node], "alter table aergolite_nodes add column test");
  db_catch(db[exec_from_node], "alter table aergolite_nodes rename to new_name");

  db_catch(db[5], "insert into aergolite_nodes (node_id,pubkey) values (111,'test')");
  db_catch(db[5], "update aergolite_nodes set last_nonce = 0");
  db_catch(db[5], "delete from aergolite_nodes");
  db_catch(db[5], "drop table aergolite_nodes");
  db_catch(db[5], "alter table aergolite_nodes add column test");
  db_catch(db[5], "alter table aergolite_nodes rename to new_name");

  db_catch(db[4], "insert into aergolite_nodes (node_id,pubkey) values (111,'test')");
  db_catch(db[4], "update aergolite_nodes set last_nonce = 0");
  db_catch(db[4], "delete from aergolite_nodes");
  db_catch(db[4], "drop table aergolite_nodes");
  db_catch(db[4], "alter table aergolite_nodes add column test");
  db_catch(db[4], "alter table aergolite_nodes rename to new_name");

  /* sqlite_master */

  db_catch(db[add_from_node], "insert into sqlite_master (name) values ('test')");
  db_catch(db[add_from_node], "update sqlite_master set name = ''");
  db_catch(db[add_from_node], "delete from sqlite_master");
  db_catch(db[add_from_node], "drop table sqlite_master");
  db_catch(db[add_from_node], "alter table sqlite_master add column test");
  db_catch(db[add_from_node], "alter table sqlite_master rename to new_name");

  db_catch(db[exec_from_node], "insert into sqlite_master (name) values ('test')");
  db_catch(db[exec_from_node], "update sqlite_master set name = ''");
  db_catch(db[exec_from_node], "delete from sqlite_master");
  db_catch(db[exec_from_node], "drop table sqlite_master");
  db_catch(db[exec_from_node], "alter table sqlite_master add column test");
  db_catch(db[exec_from_node], "alter table sqlite_master rename to new_name");

  db_catch(db[5], "insert into sqlite_master (name) values ('test')");
  db_catch(db[5], "update sqlite_master set name = ''");
  db_catch(db[5], "delete from sqlite_master");
  db_catch(db[5], "drop table sqlite_master");
  db_catch(db[5], "alter table sqlite_master add column test");
  db_catch(db[5], "alter table sqlite_master rename to new_name");

  db_catch(db[4], "insert into sqlite_master (name) values ('test')");
  db_catch(db[4], "update sqlite_master set name = ''");
  db_catch(db[4], "delete from sqlite_master");
  db_catch(db[4], "drop table sqlite_master");
  db_catch(db[4], "alter table sqlite_master add column test");
  db_catch(db[4], "alter table sqlite_master rename to new_name");

  /* non-allowed commands for normal nodes */

  db_catch(db[5], "update t1 set name = ''");
  db_catch(db[5], "delete from t1");
  db_catch(db[5], "create table t2(name)");
  db_catch(db[5], "create virtual table t2 using dbstat");
  db_catch(db[5], "drop table t1");
  db_catch(db[5], "alter table t1 add column test");
  db_catch(db[5], "alter table t1 rename to new_name");
  db_catch(db[5], "create view v1 as select * from t1");
  db_catch(db[5], "create index i1 on t1 (name)");
  db_catch(db[5], "create trigger tr1 before insert on t1 begin select raise(FAIL,''); end");



  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* exit */

  for(i=1; i<=n; i++){
    sqlite3_close(db[i]);
  }

  secp256k1_context_destroy(ecdsa_ctx);

  puts("done");

}

/****************************************************************************/

void test_add_nodes(int n, int n_each_time, int add_from_node, bool bind_to_random_ports, int block_interval){
  sqlite3 *db[512];
  char uri[256];
  char node_pubkey[512][72];
  int last_nonce[512];
  sqlite3_stmt *stmt=NULL;
  int rc, i, count, done;

  printf("test_add_nodes(nodes=%d n_each_time=%d add_from_node=%d random_ports=%s block_interval=%d)...",
         n, n_each_time, add_from_node, bind_to_random_ports ? "yes" : "no", block_interval); fflush(stdout);

  assert(n>2 && n<512);

  /* delete the db files if they exist */
  delete_files(n);
  delete_log();

  /* generate private and public keys to manage the network */
  prepare_blockchain_admin_keys();

  /* open the connections to the databases using the admin public key */

  sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&admin=%s&password=test&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[1])==SQLITE_OK );

  sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&admin=%s&password=test&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[2])==SQLITE_OK );

  for(i=3; i<=n; i++){
    if( bind_to_random_ports ){
      sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&admin=%s&password=test&block_interval=%d", i, pkstr, block_interval);
    }else{
      sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&admin=%s&password=test&block_interval=%d", i, 4300 + i, pkstr, block_interval);
    }
    //puts(uri); fflush(stdout);
    assert( sqlite3_open(uri, &db[i])==SQLITE_OK );
  }


  /* set the initial nonce value for each node */

  for(i=1; i<=n; i++){
    last_nonce[i] = 0;
  }

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* check the list of nodes connected to each of them */

  for(i=1; i<=n; i++){
    int nrows;
loc_again1:
    rc = sqlite3_prepare_v2(db[i], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );

    // node_id | pubkey | type | address | CPU | OS | hostname | app | node_info |

    assert( sqlite3_column_count(stmt)==9 );
    nrows = 0;

    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *type = (char*)sqlite3_column_text(stmt, 2);
      assert( type && strcmp(type,"unauthorized")==0 );

      /* count how many peers this node is connected to */
      nrows++;

      /* the first item has info about the requester node */
      if( nrows==1 ){
        char *p = (char*)sqlite3_column_text(stmt, 1);
        assert( p && strlen(p)<sizeof(node_pubkey[1]) );
        strcpy(node_pubkey[i], p);
      }
    }
    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;

    if( (i<=2 && nrows<n) ||
        (i>2  && nrows<3) ){
      printf("."); fflush(stdout);
      sleep_ms(wait_time);
      goto loc_again1;
    }
  }

  puts("");


  /* register the callback function used to sign the admin transactions */

  for(i=1; i<=n; i++){
    sqlite3_create_function(db[i], "sign_transaction", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
      NULL, &on_sign_transaction, NULL, NULL);
  }


  /* add nodes to the network.
  ** the command is signed in the callback function, using the network admin's private key */

  int included_nodes[512] = {0};

  while( len_array_list(included_nodes)<n ){

    /* include some nodes on the network */
    for(count=0; count<n_each_time && len_array_list(included_nodes)<n ; count++){
      char cmd[128];
      int node;
      if( len_array_list(included_nodes)==0 ){
        node = add_from_node;
      }else{
        do{
          node = random_number(1, n);
        } while( in_array_list(node,included_nodes) );
      }
      //
      printf("authorizing node %d on the blockchain network\n", node); fflush(stdout);
      sprintf(cmd, "pragma add_node='%s'", node_pubkey[node]);
      db_execute(db[add_from_node], cmd);
      add_to_array_list(included_nodes, node);
    }

    /* ensure that the nodes are included on the blockchain network */

    for(i=0; i<len_array_list(included_nodes); i++){
      int nrows;
      int node = included_nodes[i];
      printf("checking node %d connections", node); fflush(stdout);
loc_again2:
      sqlite3_finalize(stmt); stmt = NULL;
      rc = sqlite3_prepare_v2(db[node], "pragma nodes", -1, &stmt, NULL);
      assert( rc==SQLITE_OK );
      assert( stmt!=NULL );

      // node_id | pubkey | type | address | CPU | OS | hostname | app | node_info |

      assert( sqlite3_column_count(stmt)==9 );
      nrows = 0;

      while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
        char *nodepk = (char*)sqlite3_column_text(stmt, 1);
        char *type = (char*)sqlite3_column_text(stmt, 2);
        assert( nodepk && type );
        /* identify the node by the public key */
        for(int j=1; j<=n; j++){
          if( strcmp(node_pubkey[j], nodepk)==0 ){
            if( in_array_list(j,included_nodes) ){
              if( strcmp(type,"unauthorized")==0 ){
                printf("."); fflush(stdout);
                sleep_ms(wait_time);
                goto loc_again2;
              }
            }else{
              assert( strcmp(type,"unauthorized")==0 );
            }
          }
        }
#if 0
        for(int j=0; j<10; j++){
          printf("%s=%s\n", sqlite3_column_name(stmt,j), sqlite3_column_text(stmt,j));
        }
        puts("");
#endif
        /* count how many peers this node is connected to */
        nrows++;
      }
      puts("");

      assert( rc==SQLITE_DONE || rc==SQLITE_OK );
      sqlite3_finalize(stmt); stmt = NULL;

      //printf("connected to %d nodes\n", nrows);
      if( node<=2 ){
        assert( nrows==n );
      }else{
        //assert( nrows>=len_array_list(included_nodes) );
        sleep_ms(wait_time);
        if( nrows<len_array_list(included_nodes) ) goto loc_again2;
      }
    }
    //puts("");

    /* wait to add the new nodes */
    sleep_ms(block_interval);
  }



  last_nonce[add_from_node] = n;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  int exec_from_node = 3;


  /* checking if the database is ready to execute transactions */

  printf("waiting until node %d is ready\n", exec_from_node); fflush(stdout);

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db[exec_from_node], "PRAGMA db_is_ready");
    assert(rc==SQLITE_OK);
    done = (result==1);
  }
  assert(done);


  /* execute 3 db transactions on one of the databases */

  printf("executing transactions on nodes..."); fflush(stdout);

  db_execute(db[exec_from_node], "create table t1 (name)");
  db_execute(db[exec_from_node], "insert into t1 values ('aa1')");
  db_execute(db[exec_from_node], "insert into t1 values ('aa2')");

  last_nonce[exec_from_node] += 3;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* wait until the transactions are processed in a new block */

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    char *result;
    sleep_ms(wait_time);
    rc = db_query_str(&result, db[exec_from_node], "PRAGMA transaction_status(3)");
    assert(rc==SQLITE_OK);
    done = (strcmp(result,"processed")==0);
    sqlite3_free(result);
    printf("."); fflush(stdout);
  }
  assert(done);

  puts("");


  /* check if the data was replicated to the other nodes */

  for(i=1; i<=n; i++){

    printf("checking node %d\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where name='t1'");
      assert(rc==SQLITE_OK);
      done = (result>0);
    }
    assert(done);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result>1);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1", 2);
    db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);

  }


  /* checking if the node database is ready to execute transactions */

  for(i=1; i<=n; i++){
    printf("waiting until node %d is ready\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "PRAGMA db_is_ready");
      assert(rc==SQLITE_OK);
      done = (result==1);
    }
    assert(done);

  }


  /* execute more transactions on separate databases */

  puts("inserting more data..."); fflush(stdout);

  db_execute(db[4], "insert into t1 values ('aa3')");
  db_execute(db[3], "insert into t1 values ('aa4')");
  db_execute(db[5], "insert into t1 values ('aa5')");
  db_execute(db[2], "create table t2 (name)");
  db_execute(db[2], "insert into t2 values ('aa1')");

  last_nonce[4]++;
  last_nonce[3]++;
  last_nonce[5]++;
  last_nonce[2]++;
  last_nonce[2]++;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* wait until the transactions are processed in a new block */

  for(i=2; i<=5; i++){

    printf("waiting new block on node %d", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      char query[64], *result;
      sleep_ms(wait_time);
      sprintf(query, "PRAGMA transaction_status(%d)", last_nonce[i]);
      rc = db_query_str(&result, db[i], query);
      assert(rc==SQLITE_OK);
      done = (strcmp(result,"processed")==0);
      sqlite3_free(result);
      printf("."); fflush(stdout);
    }
    assert(done);

    puts("");

  }


  /* check if the data was replicated on all the nodes */

  for(i=1; i<=n; i++){

    printf("checking node %d\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where name='t2'");
      assert(rc==SQLITE_OK);
      done = (result>0);
    }
    assert(done);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result>2);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1", 5);
    db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa3'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa4'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa5'", 1);

  }


  for(i=1; i<=n; i++){
    sqlite3_close(db[i]);
  }

  secp256k1_context_destroy(ecdsa_ctx);

  puts("done");

}

/****************************************************************************/

void test_reconnection(
  int n, bool bind_to_random_ports, int block_interval,
  /* full nodes */
  int full_nodes[],
  int node_type_setting,
  /* nodes that should be disconnected */
  int disconnect_nodes[],
  /* transactions executed on disconnected nodes while in split */
  int num_txns_on_offline_nodes,
  int active_offline_nodes[],
  /* transactions executed on connected nodes while in split */
  int num_txns_on_online_nodes,
  int active_online_nodes[],
  /* transactions executed after the nodes were reconnected and in sync */
  int num_txns_on_reconnect,
  int active_nodes_on_reconnect[],
  /* additional parameters */
  int wait_between_add_nodes,
  bool check_connections,
  bool exec_while_adding
){
  sqlite3 *db[512];
  char uri[256];
  char node_pubkey[512][72];
  sqlite3_stmt *stmt=NULL;
  int rc, i, j, node, count, done;
  int num_full_nodes, num_light_nodes;
  int last_nonce[512];

  printf("----------------------------------------------------------\n"
         "test_reconnection(\n"
         "  nodes=%d\n", n);
  printf("  random_ports=%s\n", bind_to_random_ports ? "yes" : "no");
  print_nodes("  full_nodes", full_nodes);
  print_nodes("  disconnect_nodes", disconnect_nodes);
  print_nodes("  active_offline_nodes", active_offline_nodes);
  print_nodes("  active_online_nodes", active_online_nodes);
  print_nodes("  active_nodes_on_reconnect", active_nodes_on_reconnect);
  printf("  node_type_setting=%d\n", node_type_setting);
  printf("  num_txns_on_offline_nodes=%d\n", num_txns_on_offline_nodes);
  printf("  num_txns_on_online_nodes=%d\n", num_txns_on_online_nodes);
  printf("  num_txns_on_reconnect=%d\n", num_txns_on_reconnect);
  printf("  wait_between_add_nodes=%d\n", wait_between_add_nodes);
  printf("  check_connections=%s\n", check_connections ? "yes" : "no");
  printf("  exec_while_adding=%s\n", exec_while_adding ? "yes" : "no");
  puts(")");
  fflush(stdout);

  assert(n>=5 && n<512);

  num_full_nodes = len_array_list(full_nodes);
  num_light_nodes = n - num_full_nodes;

  /* delete the db files if they exist */
  delete_files(n);
  delete_log();

  /* generate private and public keys to manage the network */
  prepare_blockchain_admin_keys();

  /* open the connections to the databases */

  sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[1])==SQLITE_OK );

  sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[2])==SQLITE_OK );

  for(i=3; i<=n; i++){
    if( bind_to_random_ports ){
      sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", i, pkstr, block_interval);
    }else{
      sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", i, 4300 + i, pkstr, block_interval);
    }
    //puts(uri);
    assert( sqlite3_open(uri, &db[i])==SQLITE_OK );
    printf("."); fflush(stdout);
  }


  /* set the initial nonce value for each node */

  for(i=1; i<=n; i++){
    last_nonce[i] = 0;
  }

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* check the list of nodes connected to each of them */

  for(i=1; i<=n; i++){
    int nrows;
loc_again1:
    rc = sqlite3_prepare_v2(db[i], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );

    // node_id | pubkey | type | address | CPU | OS | hostname | app | node_info |

    assert( sqlite3_column_count(stmt)==9 );
    nrows = 0;

    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *type = (char*)sqlite3_column_text(stmt, 2);
      assert( type && strcmp(type,"unauthorized")==0 );

      /* count how many peers this node is connected to */
      nrows++;

      /* the first item has info about the requester node */
      if( nrows==1 ){
        char *p = (char*)sqlite3_column_text(stmt, 1);
        assert( p && strlen(p)<sizeof(node_pubkey[1]) );
        strcpy(node_pubkey[i], p);
      }
    }
    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;

    if( check_connections ){
      if( (i<=2 && nrows<n) ||
          (i>2  && nrows<3) ){
        printf("."); fflush(stdout);
        sleep_ms(wait_time);
        goto loc_again1;
      }
    }
  }

  puts("");


  /* register the callback function used to sign the admin transactions */

  for(i=1; i<=n; i++){
    sqlite3_create_function(db[i], "sign_transaction", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
      NULL, &on_sign_transaction, NULL, NULL);
  }


  /* add nodes to the network.
  ** the command is signed in the callback function, using the network admin's private key */

  int add_from_node = 1;

  /* include some nodes on the network */
  for(node=1; node<=n; node++){
    char cmd[128];
    printf("adding node %d to the network\n", node);
    if( node_type_setting==USE_ADD_NODE && in_array_list(node,full_nodes) ){
      sprintf(cmd, "pragma add_node='full:%s'", node_pubkey[node]);
    }else{
      sprintf(cmd, "pragma add_node='%s'", node_pubkey[node]);
    }
    db_execute(db[add_from_node], cmd);
    if( node==1 ){
      sleep_ms(block_interval * 2);
    }else{
      sleep_ms(wait_between_add_nodes);
    }
  }


  last_nonce[add_from_node] = n;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  if( node_type_setting==USE_NODE_TYPE ){
    for(node=1; node<=n; node++){
      char cmd[128];
      if( in_array_list(node,full_nodes) ){
        printf("setting node %d as full node\n", node);
        sprintf(cmd, "pragma node_type='full:%s'", node_pubkey[node]);
        db_execute(db[add_from_node], cmd);
        last_nonce[add_from_node]++;
        //sleep_ms(wait_between_add_nodes);
      }
    }
  }else if( node_type_setting==USE_NODE_TYPE_ALL ){
    printf("setting all nodes as full nodes\n");
    db_execute(db[add_from_node], "pragma node_type='full:*'");
    last_nonce[add_from_node]++;
  }


  if( check_connections ){

  /* ensure that the nodes are included on the blockchain network */

  for(node=1; node<=n; node++){
    int nrows;
    printf("checking node %d connections\n", node);
loc_again2:
    sqlite3_finalize(stmt); stmt = NULL;
    rc = sqlite3_prepare_v2(db[node], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );

    // node_id | pubkey | type | address | CPU | OS | hostname | app | node_info |

    assert( sqlite3_column_count(stmt)==9 );
    nrows = 0;

    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *nodepk = (char*)sqlite3_column_text(stmt, 1);
      char *type = (char*)sqlite3_column_text(stmt, 2);
      assert( nodepk && type );
      /* identify the node by the public key */
      for(int j=1; j<=n; j++){
        if( strcmp(node_pubkey[j], nodepk)==0 ){
          if( strcmp(type,"unauthorized")==0 ){
            printf("authorization for node %d has not arrived\n", j); fflush(stdout);
            sleep_ms(wait_time);
            goto loc_again2;
          }
        }
      }
      /* count how many peers this node is connected to */
      nrows++;
    }

    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;

    //printf("connected to %d nodes\n", nrows); fflush(stdout);
    if( node<=2 ){
      assert( nrows==n );
    }else{
      if( nrows<n ) goto loc_again2;
    }
  }


  bool has_external;
  bool missing_node_type;

loc_check_conns:
  has_external = false;
  missing_node_type = false;

  /* count how many peers each node is connected to */
  for(i=1; i<=n; i++){
    int nauths = 0, npeers = 0, nexternal = 0, nfull = 0;
    rc = sqlite3_prepare_v2(db[i], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );
    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *type = (char*)sqlite3_column_text(stmt, 2);
      char *address = (char*)sqlite3_column_text(stmt, 3);
      if( address[0]!='(' ) npeers++;
      if( strcmp(type,"unauthorized")==0 ) nexternal++;
      else nauths++;
      if( strcmp(type,"full")==0 ) nfull++;
    }
    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;
    printf("node %d connected to %d nodes (%d unauthorized). total of %d authorized nodes (%d full)\n", i, npeers-1, nexternal, nauths, nfull);
    if( nexternal>0 ) has_external = true;
    if( nfull!=num_full_nodes ) missing_node_type = true;
  }

  if( has_external || missing_node_type ){
    sleep_ms(2000);
    puts("");
    goto loc_check_conns;
  }

  }  // if( check_connections )



  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* execute 3 db transactions on one of the databases */

  puts("executing transactions on nodes..."); fflush(stdout);

  int exec_from_node = 3;

  db_execute(db[exec_from_node], "create table t1 (name)");
  db_execute(db[exec_from_node], "insert into t1 values ('aa1')");
  db_execute(db[exec_from_node], "insert into t1 values ('aa2')");

  last_nonce[exec_from_node] += 3;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* wait until the transactions are processed in a new block */

  printf("waiting for new block"); fflush(stdout);

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    char *result;
    sleep_ms(wait_time);
    rc = db_query_str(&result, db[exec_from_node], "PRAGMA transaction_status(3)");
    assert(rc==SQLITE_OK);
    done = (strcmp(result,"processed")==0);
    sqlite3_free(result);
    printf("."); fflush(stdout);
  }
  assert(done);

  puts("");


  /* check if the data was replicated to the other nodes */

  for(i=1; i<=n; i++){

    printf("checking node %d\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where name='t1'");
      assert(rc==SQLITE_OK);
      done = (result>0);
    }
    assert(done);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result>1);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1", 2);
    db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);

  }


  for(i=1; i<=n; i++){

    printf("checking node %d\n", i); fflush(stdout);

    if( last_nonce[i] > 0 ){
      char sql[128];
      sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[i]);
      //db_check_str(db[i], sql, "processed");
      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        char *result;
        if( count>0 ) sleep_ms(wait_time);
        rc = db_query_str(&result, db[i], sql);
        assert(rc==SQLITE_OK);
        done = (strcmp(result,"processed")==0);
        sqlite3_free(result);
      }
      assert(done);
    }

  }


  /* disconnect some nodes */

  for(i=0; disconnect_nodes[i]; i++){
    int node = disconnect_nodes[i];
    printf("disconnecting node %d\n", node);
    sqlite3_close(db[node]);
    db[node] = 0;
  }


  /* execute transactions on online nodes */

  if( num_txns_on_online_nodes>0 ){
    assert(len_array_list(active_online_nodes)>0);

    puts("executing new transactions on online nodes...");

    for(j=0, i=0; j<num_txns_on_online_nodes; j++, i++){
      int node = active_online_nodes[i];
      if( node==0 ){
        i = 0;
        node = active_online_nodes[i];
      }
      printf("executing on node %d\n", node);

      db_check_int(db[node], "PRAGMA db_is_ready", 1);

      db_execute(db[node], "insert into t1 values ('online')");

      last_nonce[node]++;
      db_check_int(db[node], "PRAGMA last_nonce", last_nonce[node]);
    }

    if( n-len_array_list(disconnect_nodes) >= majority(n) ){

      /* wait until the transactions are processed in a new block */

      printf("waiting for new block"); fflush(stdout);

      for(i=0; active_online_nodes[i]; i++){
        int node = active_online_nodes[i];

        done = 0;
        for(count=0; !done && count<wait_repeat; count++){
          char *result, sql[128];
          if( count>0 ) sleep_ms(wait_time);
          sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[node]);
          rc = db_query_str(&result, db[node], sql);
          assert(rc==SQLITE_OK);
          done = (strcmp(result,"processed")==0);
          sqlite3_free(result);
          printf("."); fflush(stdout);
        }
        assert(done);

      }

      puts("");

      /* check if the data was replicated to the other nodes */

      for(i=1; i<=n; i++){

        if( in_array_list(i,disconnect_nodes) ) continue;

        printf("checking node %d\n", i); fflush(stdout);

        done = 0;
        for(count=0; !done && count<wait_repeat; count++){
          int result;
          if( count>0 ) sleep_ms(wait_time);
          rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where name='t1'");
          assert(rc==SQLITE_OK);
          done = (result>0);
        }
        assert(done);

        done = 0;
        for(count=0; !done && count<wait_repeat; count++){
          int result;
          if( count>0 ) sleep_ms(wait_time);
          rc = db_query_int32(&result, db[i], "select count(*) from t1");
          assert(rc==SQLITE_OK);
          done = (result >= 2 + num_txns_on_online_nodes);
        }
        assert(done);

        db_check_int(db[i], "select count(*) from t1", 2 + num_txns_on_online_nodes);
        db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
        db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);
        db_check_int(db[i], "select count(*) from t1 where name='online'", num_txns_on_online_nodes);

      }

    }

  }


  /* execute transactions on offline nodes */

  if( num_txns_on_offline_nodes>0 ){
    assert(len_array_list(active_offline_nodes)>0);

    /* reopen the nodes in off-line mode */

    for(i=0; active_offline_nodes[i]; i++){
      int node = active_offline_nodes[i];
      printf("reopening node %d in offline mode\n", node);
      sprintf(uri, "file:db%d.db?blockchain=on&password=test&admin=%s&block_interval=%d", node, pkstr, block_interval);
      assert( sqlite3_open(uri, &db[node])==SQLITE_OK );
    }

    puts("executing new transactions on offline nodes...");

    for(j=0, i=0; j<num_txns_on_offline_nodes; j++, i++){
      int node = active_offline_nodes[i];
      if( node==0 ){
        i = 0;
        node = active_offline_nodes[i];
      }
      printf("executing on node %d\n", node);

      db_check_int(db[node], "PRAGMA db_is_ready", 1);

      db_execute(db[node], "insert into t1 values ('offline')");

      last_nonce[node]++;
      db_check_int(db[node], "PRAGMA last_nonce", last_nonce[node]);

      //db_check_int(db[node], "select count(*) from t1", 2 + num_txns_on_offline_nodes);
      //db_check_int(db[node], "select count(*) from t1 where name='aa1'", 1);
      //db_check_int(db[node], "select count(*) from t1 where name='aa2'", 1);
      //db_check_int(db[node], "select count(*) from t1 where name='offline'", num_txns_on_offline_nodes);
    }

    /* close the off-line nodes */

    for(i=0; active_offline_nodes[i]; i++){
      int node = active_offline_nodes[i];
      sqlite3_close(db[node]);
      db[node] = 0;
    }

  }


  /* reconnect the nodes */

  for(i=0; disconnect_nodes[i]; i++){
    int node = disconnect_nodes[i];
    printf("reconnecting node %d\n", node);
    if( node==1 ){
      sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
    }else if( node==2 ){
      sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
    }else{
      if( bind_to_random_ports ){
        sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", node, pkstr, block_interval);
      }else{
        sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", node, 4300 + node, pkstr, block_interval);
      }
    }
    assert( sqlite3_open(uri, &db[node])==SQLITE_OK );
  }


  bool has_external;

loc_check_conns2:
  has_external = false;

  /* count how many peers each node is connected to */
  for(i=1; i<=n; i++){
    int nauths = 0, npeers = 0, nexternal = 0;
    rc = sqlite3_prepare_v2(db[i], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );
    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *type = (char*)sqlite3_column_text(stmt, 2);
      char *address = (char*)sqlite3_column_text(stmt, 3);
      if( address[0]!='(' ) npeers++;
      if( strcmp(type,"unauthorized")==0 ){ nexternal++; /* printf(" -- external: %s\n", address); */ }
      else nauths++;
    }
    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;
    printf("node %d connected to %d nodes (%d unauthorized). total of %d authorized nodes\n", i, npeers-1, nexternal, nauths);
    if( nexternal>0 ) has_external = true;
  }

  if( has_external ){
    sleep_ms(2000);
    puts("");
    goto loc_check_conns2;
  }


  /* check if they are up-to-date */

  for(i=0; disconnect_nodes[i]; i++){
    int node = disconnect_nodes[i];

    printf("checking node %d\n", node); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[node], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result >= 2 + num_txns_on_online_nodes + num_txns_on_offline_nodes);
    }
    if( !done ){
      for(i=1; i<=n; i++){
        char *result;
        rc = db_query_str(&result, db[i], "pragma blockchain_status");
        assert(rc==SQLITE_OK);
        printf("--- node %d ---\n", i);
        puts(result);
        puts("");
        sqlite3_free(result);
      }
    }
    assert(done);

    db_check_int(db[node], "select count(*) from t1", 2 + num_txns_on_online_nodes + num_txns_on_offline_nodes);
    db_check_int(db[node], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[node], "select count(*) from t1 where name='aa2'", 1);
    db_check_int(db[node], "select count(*) from t1 where name='offline'", num_txns_on_offline_nodes);
    db_check_int(db[node], "select count(*) from t1 where name='online'", num_txns_on_online_nodes);

  }


  /* check if the data was replicated to the other nodes */

  for(i=1; i<=n; i++){

    if( in_array_list(i,disconnect_nodes) ) continue;

    printf("checking node %d\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result >= 2 + num_txns_on_online_nodes + num_txns_on_offline_nodes);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1", 2 + num_txns_on_online_nodes + num_txns_on_offline_nodes);
    db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='offline'", num_txns_on_offline_nodes);
    db_check_int(db[i], "select count(*) from t1 where name='online'", num_txns_on_online_nodes);

  }


  /* checking if the node database is ready to execute transactions */

  for(i=1; i<=n; i++){
    printf("waiting until node %d is ready\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "PRAGMA db_is_ready");
      assert(rc==SQLITE_OK);
      done = (result==1);
    }
    assert(done);

  }


  /* execute new transactions after reconnection */

  if( num_txns_on_reconnect>0 ){
    assert(len_array_list(active_nodes_on_reconnect)>0);

    puts("executing new transactions after reconnection...");

    for(j=0, i=0; j<num_txns_on_reconnect; j++, i++){
      int node = active_nodes_on_reconnect[i];
      if( node==0 ){
        i = 0;
        node = active_nodes_on_reconnect[i];
      }
      printf("executing on node %d\n", node);

      db_execute(db[node], "insert into t1 values ('reconnect')");

      last_nonce[node]++;
      db_check_int(db[node], "PRAGMA last_nonce", last_nonce[node]);
    }

    /* wait until the transactions are processed in a new block */

    printf("waiting for new block"); fflush(stdout);

    for(i=0; active_nodes_on_reconnect[i]; i++){
      int node = active_nodes_on_reconnect[i];

      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        char *result, sql[128];
        if( count>0 ) sleep_ms(wait_time);
        sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[node]);
        rc = db_query_str(&result, db[node], sql);
        assert(rc==SQLITE_OK);
        done = (strcmp(result,"processed")==0);
        sqlite3_free(result);
        printf("."); fflush(stdout);
      }
      assert(done);

    }

    puts("");

    /* check if the data was replicated to the other nodes */

    for(i=1; i<=n; i++){

      printf("checking node %d\n", i); fflush(stdout);

      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        int result;
        if( count>0 ) sleep_ms(wait_time);
        rc = db_query_int32(&result, db[i], "select count(*) from t1");
        assert(rc==SQLITE_OK);
        done = (result >= 2 + num_txns_on_online_nodes + num_txns_on_offline_nodes + num_txns_on_reconnect);
      }
      assert(done);

      db_check_int(db[i], "select count(*) from t1", 2 + num_txns_on_online_nodes + num_txns_on_offline_nodes + num_txns_on_reconnect);
      db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
      db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);
      db_check_int(db[i], "select count(*) from t1 where name='offline'", num_txns_on_offline_nodes);
      db_check_int(db[i], "select count(*) from t1 where name='online'", num_txns_on_online_nodes);
      db_check_int(db[i], "select count(*) from t1 where name='reconnect'", num_txns_on_reconnect);

    }

  }

  /* close the db connections */

  for(i=1; i<=n; i++){
    sqlite3_close(db[i]);
  }

  secp256k1_context_destroy(ecdsa_ctx);

  puts("done");

}

/****************************************************************************/

void test_new_nodes(
  int n_before, bool bind_to_random_ports, int block_interval,
  /* which node will make the first transactions */
  int starting_node,
  /* new nodes added to the network after it is running */
  int n_new_nodes,
  /* nodes that are disconnected and reconnected */
  int n_old_no_content,
  int n_old_with_content,
  /* blocks generated while some nodes were offline */
  int new_blocks_on_net,
  int num_offline_txns,

  /* number of nodes remaining online later, less than the majority of nodes */
  int n_remaining_online,
  /* new nodes added while the network has less than half nodes */
  int n_new_nodes2
){
  sqlite3 *db[512] = {0};
  char uri[256];
  char node_pubkey[512][72];
  int last_nonce[512];
  int disconnect_nodes[512] = {0};
  int active_online_nodes[512] = {0};
  int connecting_nodes[512] = {0};
  sqlite3_stmt *stmt=NULL;
  int rc, i, j, n, node, count, done;

  printf("----------------------------------------------------------\n"
         "test_new_nodes(\n"
         "  initial_nodes=%d\n", n_before);
  printf("  random_ports=%s\n", bind_to_random_ports ? "yes" : "no");
  printf("  n_new_nodes=%d\n", n_new_nodes);
  printf("  n_old_no_content=%d\n", n_old_no_content);
  printf("  n_old_with_content=%d\n", n_old_with_content);
  printf("  num_offline_txns=%d\n", num_offline_txns);
  printf("  new_blocks_on_net=%d\n", new_blocks_on_net);

  printf("  n_remaining_online=%d\n", n_remaining_online);
  printf("  n_new_nodes2=%d\n", n_new_nodes2);
  puts(")");
  fflush(stdout);

  n = n_before + n_new_nodes + n_new_nodes2;

  assert(n>=5 && n<512);

  /* delete the db files if they exist */
  delete_files(n);
  delete_log();

  /* generate private and public keys to manage the network */
  prepare_blockchain_admin_keys();

  /* open the connections to the databases */

  sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[1])==SQLITE_OK );

  sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[2])==SQLITE_OK );

  for(i=3; i<=n_before; i++){
    if( bind_to_random_ports ){
      sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", i, pkstr, block_interval);
    }else{
      sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", i, 4300 + i, pkstr, block_interval);
    }
    //puts(uri);
    assert( sqlite3_open(uri, &db[i])==SQLITE_OK );
  }


  /* set the initial nonce value for each node */

  for(i=1; i<=n; i++){
    last_nonce[i] = 0;
  }

  for(i=1; i<=n_before; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* get the public key for the new added nodes */
  for(i=1; i<=n_before; i++){
    rc = sqlite3_prepare_v2(db[i], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );
    assert( sqlite3_column_count(stmt)==9 );
    assert( sqlite3_step(stmt)==SQLITE_ROW );
    /* the first item has info about the requester node */
    char *p = (char*)sqlite3_column_text(stmt, 1);
    assert( p && strlen(p)<sizeof(node_pubkey[1]) );
    strcpy(node_pubkey[i], p);
    sqlite3_finalize(stmt); stmt = NULL;
  }


  /* register the callback function used to sign the admin transactions */

  for(i=1; i<=n_before; i++){
    sqlite3_create_function(db[i], "sign_transaction", 1, SQLITE_UTF8,
      NULL, &on_sign_transaction, NULL, NULL);
  }


  /* add nodes to the network.
  ** the command is signed in the callback function, using the network admin's private key */

  int add_from_node = 2;

  /* include some nodes on the network */
  node = add_from_node;
  {
    char cmd[128];
    printf("adding node %d to the network\n", node);
    sprintf(cmd, "pragma add_node='%s'", node_pubkey[node]);
    db_execute(db[add_from_node], cmd);
  }
  for(node=1; node<=n_before; node++){
    char cmd[128];
    if( node==add_from_node ) continue;
    printf("adding node %d to the network\n", node);
    sprintf(cmd, "pragma add_node='%s'", node_pubkey[node]);
    db_execute(db[add_from_node], cmd);
  }

  /* ensure that the nodes are included on the blockchain network */

  for(node=1; node<=n_before; node++){
    int nrows;
    printf("checking node %d connections\n", node);
loc_again2:
    sqlite3_finalize(stmt); stmt = NULL;
    rc = sqlite3_prepare_v2(db[node], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );

    // node_id | pubkey | type | address | CPU | OS | hostname | app | node_info |

    assert( sqlite3_column_count(stmt)==9 );
    nrows = 0;

    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *nodepk = (char*)sqlite3_column_text(stmt, 1);
      char *type = (char*)sqlite3_column_text(stmt, 2);
      assert( nodepk && type );
      /* identify the node by the public key */
      for(int j=1; j<=n_before; j++){
        if( strcmp(node_pubkey[j], nodepk)==0 ){
          if( strcmp(type,"unauthorized")==0 ){
            printf("authorization for node %d has not arrived\n", j); fflush(stdout);
            sleep_ms(wait_time);
            goto loc_again2;
          }
        }
      }
      /* count how many peers this node is connected to */
      nrows++;
    }

    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;

    //printf("connected to %d nodes\n", nrows); fflush(stdout);
    if( node<=2 ){
      assert( nrows==n_before );
    }else{
      if( nrows<n_before ) goto loc_again2;
    }
  }



  last_nonce[add_from_node] = n_before;

  for(i=1; i<=n_before; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* checking if the database is ready to execute transactions */

  printf("waiting until node %d is ready\n", starting_node); fflush(stdout);

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    int result;
    if( count>0 ) sleep_ms(wait_time);
    rc = db_query_int32(&result, db[starting_node], "PRAGMA db_is_ready");
    assert(rc==SQLITE_OK);
    done = (result==1);
  }
  assert(done);


  /* execute 3 db transactions on one of the databases */

  printf("executing transactions on nodes...");

  db_execute(db[starting_node], "create table t1 (name)");
  db_execute(db[starting_node], "insert into t1 values ('aa1')");
  db_execute(db[starting_node], "insert into t1 values ('aa2')");

  last_nonce[starting_node] += 3;

  for(i=1; i<=n_before; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* wait until the transactions are processed in a new block */

  done = 0;
  for(count=0; !done && count<wait_repeat; count++){
    char query[64], *result;
    sleep_ms(wait_time);
    sprintf(query, "PRAGMA transaction_status(%d)", last_nonce[starting_node]);
    rc = db_query_str(&result, db[starting_node], query);
    assert(rc==SQLITE_OK);
    done = (strcmp(result,"processed")==0);
    sqlite3_free(result);
    printf("."); fflush(stdout);
  }
  assert(done);

  puts("");


  /* check if the data was replicated to the other nodes */

  for(i=1; i<=n_before; i++){

    printf("checking node %d\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where name='t1'");
      assert(rc==SQLITE_OK);
      done = (result>0);
    }
    assert(done);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result>1);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1", 2);
    db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);

    if( last_nonce[i] > 0 ){
      char sql[128];
      sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[i]);
      //db_check_str(db[i], sql, "processed");
      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        char *result;
        if( count>0 ) sleep_ms(wait_time);
        rc = db_query_str(&result, db[i], sql);
        assert(rc==SQLITE_OK);
        done = (strcmp(result,"processed")==0);
        sqlite3_free(result);
      }
      assert(done);
    }

  }


  /* disconnect some nodes */

  int n_first_disconnect = n_old_no_content + n_old_with_content;

  for(i=0; i<n_first_disconnect; i++){
    int node;
    do{
      node = random_number(1, n_before);
    }while( in_array_list(node,disconnect_nodes) || node==2 ); /* does not disconnect node 2 */
    add_to_array_list(disconnect_nodes, node);
    printf("disconnecting node %d\n", node);
    sqlite3_close(db[node]);
    db[node] = 0;
  }

  for(i=1; i<=n_before; i++){
    if( !in_array_list(i,disconnect_nodes) ){
      add_to_array_list(active_online_nodes, i);
    }
  }


  /* execute transactions on online nodes */

  int num_txns_per_block = num_offline_txns;

  for(int num_blocks=1; num_blocks<=new_blocks_on_net; num_blocks++){
    assert(len_array_list(active_online_nodes)>0);

    puts("executing new transactions on online nodes...");

    for(j=0, i=0; j<num_txns_per_block; j++, i++){
      int node = active_online_nodes[i];
      if( node==0 ){
        i = 0;
        node = active_online_nodes[i];
      }

      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        int result;
        if( count>0 ) sleep_ms(wait_time);
        rc = db_query_int32(&result, db[node], "PRAGMA db_is_ready");
        assert(rc==SQLITE_OK);
        done = (result==1);
      }
      assert(done);

      printf("executing on node %d\n", node);

      db_execute(db[node], "insert into t1 values ('online')");

      last_nonce[node]++;
      db_check_int(db[node], "PRAGMA last_nonce", last_nonce[node]);
    }

    /* wait until the transactions are processed in a new block */

    printf("waiting for new block"); fflush(stdout);

    for(i=0; active_online_nodes[i]; i++){
      int node = active_online_nodes[i];
      if( last_nonce[node]==0 ) continue;

      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        char *result, sql[128];
        if( count>0 ) sleep_ms(wait_time);
        sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[node]);
        rc = db_query_str(&result, db[node], sql);
        assert(rc==SQLITE_OK);
        done = (strcmp(result,"processed")==0);
        sqlite3_free(result);
        printf("."); fflush(stdout);
      }
      assert(done);

    }

    puts("");

    /* check if the data was replicated to the other nodes */

    for(i=1; i<=n_before; i++){

      if( in_array_list(i,disconnect_nodes) ) continue;

      printf("checking node %d\n", i); fflush(stdout);

      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        int result;
        if( count>0 ) sleep_ms(wait_time);
        rc = db_query_int32(&result, db[i], "select count(*) from t1");
        assert(rc==SQLITE_OK);
        done = (result >= 2 + num_blocks * num_txns_per_block);
      }
      assert(done);

      db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
      db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);
      db_check_int(db[i], "select count(*) from t1 where name='online'", num_blocks * num_txns_per_block);
      db_check_int(db[i], "select count(*) from t1", 2 + num_blocks * num_txns_per_block);

    }

  }


  /* execute transactions on disconnected nodes */

  if( n_old_with_content>0 ){

    /* reopen the nodes in off-line mode */

    for(i=0; i<n_old_with_content; i++){
      int node = disconnect_nodes[i];
      printf("reopening node %d in offline mode\n", node);
      sprintf(uri, "file:db%d.db?blockchain=on&password=test&admin=%s&block_interval=%d", node, pkstr, block_interval);
      assert( sqlite3_open(uri, &db[node])==SQLITE_OK );
    }

    puts("executing new transactions on offline nodes...");

    for(j=0, i=0; j<n_old_with_content*num_offline_txns; j++, i++){
      int node = disconnect_nodes[i];
      if( node==0 || db[node]==0 ){
        i = 0;
        node = disconnect_nodes[i];
      }

      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        int result;
        if( count>0 ) sleep_ms(wait_time);
        rc = db_query_int32(&result, db[node], "PRAGMA db_is_ready");
        assert(rc==SQLITE_OK);
        done = (result==1);
      }
      assert(done);

      printf("executing on node %d\n", node);

      db_execute(db[node], "insert into t1 values ('disconnected')");

      last_nonce[node]++;
      db_check_int(db[node], "PRAGMA last_nonce", last_nonce[node]);
    }

    /* close the off-line nodes */

    for(i=0; i<n_old_with_content; i++){
      int node = disconnect_nodes[i];
      sqlite3_close(db[node]);
      db[node] = 0;
    }

  }


  /* get the list of connecting nodes */

  for(i=0; disconnect_nodes[i]; i++){
    int node = disconnect_nodes[i];
    add_to_array_list(connecting_nodes, node);
  }

  for(i=n_before+1; i<=n_before+n_new_nodes; i++){
    add_to_array_list(connecting_nodes, i);
  }


  /* reconnect the nodes */

  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];
    if( node>n_before ){
      printf("connecting new node %d\n", node);
    }else{
      printf("reconnecting node %d\n", node);
    }
    if( node==1 ){
      sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
    }else if( node==2 ){
      sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
    }else{
      if( bind_to_random_ports ){
        sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", node, pkstr, block_interval);
      }else{
        sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", node, 4300 + node, pkstr, block_interval);
      }
    }
    assert( sqlite3_open(uri, &db[node])==SQLITE_OK );
  }

  /* check the last nonce */
  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];
    db_check_int(db[node], "PRAGMA last_nonce", last_nonce[node]);
  }

  /* register the callback function used to sign the admin transactions */
  sqlite3_create_function(db[add_from_node], "sign_transaction", 1, SQLITE_UTF8,
    NULL, &on_sign_transaction, NULL, NULL);

  /* get the public key for the new added nodes */
  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];
    if( node<=n_before ) continue;
    rc = sqlite3_prepare_v2(db[node], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );
    assert( sqlite3_column_count(stmt)==9 );
    assert( sqlite3_step(stmt)==SQLITE_ROW );
    /* the first item has info about the requester node */
    char *p = (char*)sqlite3_column_text(stmt, 1);
    assert( p && strlen(p)<sizeof(node_pubkey[1]) );
    strcpy(node_pubkey[node], p);
    sqlite3_finalize(stmt); stmt = NULL;
  }

  /* authorize the new nodes on the network */
  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];
    if( node>n_before ){
      char cmd[128];
      printf("authorizing node %d on the network\n", node);
      sprintf(cmd, "pragma add_node='%s'", node_pubkey[node]);
      db_execute(db[add_from_node], cmd);
      last_nonce[add_from_node]++;
    }
  }


  /* wait until the transactions are processed in a new block */

  printf("waiting for new block\n"); fflush(stdout);

  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];

    printf("checking node %d", node); fflush(stdout);

    if( last_nonce[node]==0 ){ puts(""); continue; }

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      char *result, sql[128];
      if( count>0 ) sleep_ms(wait_time);
      sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[node]);
      rc = db_query_str(&result, db[node], sql);
      assert(rc==SQLITE_OK);
      done = (strcmp(result,"processed")==0);
      sqlite3_free(result);
      printf("."); fflush(stdout);
    }
    assert(done);

    puts("");
  }


  /* check if they are up-to-date */

  puts("checking content"); fflush(stdout);

  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];

    printf("checking node %d\n", node); fflush(stdout);

    int total_rows = 2 + new_blocks_on_net * num_txns_per_block + n_old_with_content * num_offline_txns;

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[node], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result >= total_rows);
    }
    assert(done);

    int total_tables = num_system_tables + 1;

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[node], "select count(*) from sqlite_master where type='table'");
      assert(rc==SQLITE_OK);
      done = (result >= total_tables);
    }
    assert(done);

    db_check_int(db[node], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[node], "select count(*) from t1 where name='aa2'", 1);
    db_check_int(db[node], "select count(*) from t1 where name='online'", new_blocks_on_net * num_txns_per_block);
    db_check_int(db[node], "select count(*) from t1 where name='disconnected'", n_old_with_content * num_offline_txns);
    db_check_int(db[node], "select count(*) from t1", total_rows);
    db_check_int(db[node], "select count(*) from sqlite_master where type='table'", total_tables);

  }


  /* check if the data was replicated to the other nodes */

  for(i=1; i<=n_before; i++){

    if( in_array_list(i,connecting_nodes) ) continue;

    printf("checking node %d\n", i); fflush(stdout);

    int total_rows = 2 + new_blocks_on_net * num_txns_per_block + n_old_with_content * num_offline_txns;

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result >= total_rows);
    }
    assert(done);

    int total_tables = num_system_tables + 1;

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where type='table'");
      assert(rc==SQLITE_OK);
      done = (result >= total_tables);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);
    db_check_int(db[i], "select count(*) from t1 where name='online'", new_blocks_on_net * num_txns_per_block);
    db_check_int(db[i], "select count(*) from t1 where name='disconnected'", n_old_with_content * num_offline_txns);
    db_check_int(db[i], "select count(*) from t1", total_rows);
    db_check_int(db[i], "select count(*) from sqlite_master where type='table'", total_tables);

  }




  // join while the network was having less than majority
  // join while the network has less than majority, and keep having no majority



  int n_after = n_before + n_new_nodes;

  int n_disconnect = n_after - n_remaining_online;

  if( n_disconnect==0 ) goto loc_exit;


  /* disconnect many nodes until the network has no majority online */

  clear_array_list(disconnect_nodes);

  for(i=0; i<n_disconnect; i++){
    int node;
    do{
      node = random_number(1, n_after);
    }while( in_array_list(node,disconnect_nodes) || node==2 ); /* does not disconnect node 2 */
    add_to_array_list(disconnect_nodes, node);
    printf("disconnecting node %d\n", node);
    sqlite3_close(db[node]);
    db[node] = 0;
  }

  clear_array_list(active_online_nodes);

  for(i=1; i<=n_after; i++){
    if( !in_array_list(i,disconnect_nodes) ){
      add_to_array_list(active_online_nodes, i);
    }
  }



  /* execute transactions on the remaining online nodes */

  puts("executing transactions on remaining online nodes...");

  for(j=0, i=0; j<num_offline_txns; j++, i++){
    int node = active_online_nodes[i];
    if( node==0 ){
      i = 0;
      node = active_online_nodes[i];
    }
    printf("executing on node %d\n", node);

    db_execute(db[node], "insert into t1 values ('no_quorum')");

    last_nonce[node]++;
    db_check_int(db[node], "PRAGMA last_nonce", last_nonce[node]);
  }

  /* wait until the transactions are processed in a new block */

  printf("checking that no new block is generated"); fflush(stdout);

  done = 0;
  for(count=0, i=0; !done && count<50; count++){
    int node = active_online_nodes[i];
    if( node==0 ){
      i = 0;
      node = active_online_nodes[i];
    }
    char *result, sql[128];
    sleep_ms(wait_time);
    sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[node]);
    rc = db_query_str(&result, db[node], sql);
    assert(rc==SQLITE_OK);
    done = (strcmp(result,"processed")==0);
    sqlite3_free(result);
    printf("."); fflush(stdout);
  }
  assert(done==0);

  puts("");


  /* get the list of connecting nodes */

  clear_array_list(connecting_nodes);

  //for(i=0; disconnect_nodes[i]; i++){
  //  int node = disconnect_nodes[i];
  //  add_to_array_list(connecting_nodes, node);
  //}

  int n_after2 = n_after + n_new_nodes2;

  for(i=n_after+1; i<=n_after2; i++){
    add_to_array_list(connecting_nodes, i);
    add_to_array_list(active_online_nodes, i);
  }


  /* reconnect the nodes */

  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];
    if( node>n_after ){
      printf("connecting new node %d\n", node);
    }else{
      printf("reconnecting node %d\n", node);
    }
    if( node==1 ){
      sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
    }else if( node==2 ){
      sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
    }else{
      if( bind_to_random_ports ){
        sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", node, pkstr, block_interval);
      }else{
        sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", node, 4300 + node, pkstr, block_interval);
      }
    }
    assert( sqlite3_open(uri, &db[node])==SQLITE_OK );
  }

  /* check the last nonce */
  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];
    db_check_int(db[node], "PRAGMA last_nonce", last_nonce[node]);
  }

  /* register the callback function used to sign the admin transactions */
  assert( db[add_from_node] );
  sqlite3_create_function(db[add_from_node], "sign_transaction", 1, SQLITE_UTF8,
    NULL, &on_sign_transaction, NULL, NULL);

  /* get the public key for the new added nodes */
  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];
    if( node<=n_after ) continue;
    rc = sqlite3_prepare_v2(db[node], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );
    assert( sqlite3_column_count(stmt)==9 );
    assert( sqlite3_step(stmt)==SQLITE_ROW );
    /* the first item has info about the requester node */
    char *p = (char*)sqlite3_column_text(stmt, 1);
    assert( p && strlen(p)<sizeof(node_pubkey[1]) );
    strcpy(node_pubkey[node], p);
    sqlite3_finalize(stmt); stmt = NULL;
  }

  /* count how many peers each node is connected to */
  for(i=0; active_online_nodes[i]; i++){
    int node = active_online_nodes[i];
    int nauths = 0, npeers = 0;
    rc = sqlite3_prepare_v2(db[node], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );
    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *address = (char*)sqlite3_column_text(stmt, 3);
      if( address[0]!='(' ) npeers++;
      nauths++;
    }
    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;
    printf("node %d connected to %d nodes. total of %d authorizations\n", node, npeers-1, nauths);
  }

  /* authorize the new nodes on the network */
  for(i=0; connecting_nodes[i]; i++){
    int node = connecting_nodes[i];
    if( node>n_after ){
      char cmd[128];
      printf("authorizing node %d on the network\n", node);
      sprintf(cmd, "pragma add_node='%s'", node_pubkey[node]);
      db_execute(db[add_from_node], cmd);
      last_nonce[add_from_node]++;
    }
  }



  //if( n_after < majority(n) ){

    printf("checking that no new block is generated"); fflush(stdout);

    done = 0;
    for(count=0, i=0; !done && count<50; count++){
      int node = active_online_nodes[i];
      if( node==0 ){
        i = 0;
        node = active_online_nodes[i];
      }
      char *result, sql[128];
      sleep_ms(wait_time);
      sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[node]);
      rc = db_query_str(&result, db[node], sql);
      assert(rc==SQLITE_OK);
      done = (strcmp(result,"processed")==0);
      sqlite3_free(result);
      printf("."); fflush(stdout);
    }
    puts("");
    assert(done==0);


    for(i=0; disconnect_nodes[i]; i++){
      int node = disconnect_nodes[i];
      printf("reconnecting node %d\n", node);
      if( node==1 ){
        sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
      }else if( node==2 ){
        sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&password=test&admin=%s&block_interval=%d", pkstr, block_interval);
      }else{
        if( bind_to_random_ports ){
          sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", node, pkstr, block_interval);
        }else{
          sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&password=test&admin=%s&block_interval=%d", node, 4300 + node, pkstr, block_interval);
        }
      }
      assert( sqlite3_open(uri, &db[node])==SQLITE_OK );
    }

    clear_array_list(disconnect_nodes);

  //}


  /* check if the transactions are processed in a new block */

  if( n_after2 >= majority(n) ){

    /* count how many peers each node is connected to */
    for(i=0; active_online_nodes[i]; i++){
      int node = active_online_nodes[i];
      int nauths = 0, npeers = 0;
      rc = sqlite3_prepare_v2(db[node], "pragma nodes", -1, &stmt, NULL);
      assert( rc==SQLITE_OK );
      assert( stmt!=NULL );
      while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
        char *address = (char*)sqlite3_column_text(stmt, 3);
        if( address[0]!='(' ) npeers++;
        nauths++;
      }
      assert( rc==SQLITE_DONE || rc==SQLITE_OK );
      sqlite3_finalize(stmt); stmt = NULL;
      printf("node %d connected to %d nodes. total of %d authorizations\n", node, npeers-1, nauths);
    }

    printf("waiting for new block"); fflush(stdout);

    for(i=0; active_online_nodes[i]; i++){
      int node = active_online_nodes[i];
      if( last_nonce[node]==0 ) continue;
      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        char *result, sql[128];
        if( count>0 ) sleep_ms(wait_time);
        sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[node]);
        rc = db_query_str(&result, db[node], sql);
        assert(rc==SQLITE_OK);
        done = (strcmp(result,"processed")==0);
        sqlite3_free(result);
        printf("."); fflush(stdout);
      }
      assert(done);
    }
    puts("");


    /* check if the data was replicated to the other nodes */

    int num_records = 2 + new_blocks_on_net * num_txns_per_block
                        + n_old_with_content * num_offline_txns
                        + num_offline_txns;

    int num_tables = num_system_tables + 1;

    for(i=1; i<=n_after2; i++){

      if( in_array_list(i,disconnect_nodes) ) continue;

      printf("checking node %d\n", i); fflush(stdout);

      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        int result;
        if( count>0 ) sleep_ms(wait_time);
        rc = db_query_int32(&result, db[i], "select count(*) from t1");
        assert(rc==SQLITE_OK);
        done = (result >= num_records);
      }
      assert(done);

      done = 0;
      for(count=0; !done && count<wait_repeat; count++){
        int result;
        if( count>0 ) sleep_ms(wait_time);
        rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where type='table'");
        assert(rc==SQLITE_OK);
        done = (result >= num_tables);
      }
      assert(done);

      db_check_int(db[i], "select count(*) from t1 where name='aa1'", 1);
      db_check_int(db[i], "select count(*) from t1 where name='aa2'", 1);
      db_check_int(db[i], "select count(*) from t1 where name='online'", new_blocks_on_net * num_txns_per_block);
      db_check_int(db[i], "select count(*) from t1 where name='disconnected'", n_old_with_content * num_offline_txns);
      db_check_int(db[i], "select count(*) from t1 where name='no_quorum'", num_offline_txns);
      db_check_int(db[i], "select count(*) from sqlite_master where type='table'", num_tables);
      db_check_int(db[i], "select count(*) from t1", num_records);
    }

  }else{  /* no majority of nodes on the network */

    printf("checking that no new block is generated"); fflush(stdout);

    done = 0;
    for(count=0, i=0; !done && count<wait_repeat; count++){
      int node = active_online_nodes[i];
      if( node==0 ){
        i = 0;
        node = active_online_nodes[i];
      }
      char *result, sql[128];
      sleep_ms(wait_time);
      sprintf(sql, "PRAGMA transaction_status(%d)", last_nonce[node]);
      rc = db_query_str(&result, db[node], sql);
      assert(rc==SQLITE_OK);
      done = (strcmp(result,"processed")==0);
      sqlite3_free(result);
      printf("."); fflush(stdout);
    }
    puts("");
    assert(done==0);

  }


loc_exit:

  /* close the db connections */

  for(i=1; i<=n; i++){
    sqlite3_close(db[i]);
  }

  secp256k1_context_destroy(ecdsa_ctx);

  puts("done");

}

/****************************************************************************/

void test_incoming_txn_visibility(int n, int txn_interval, int block_interval, bool bind_to_random_ports){
  sqlite3 *db[512];
  char uri[256];
  char node_pubkey[512][72];
  int last_nonce[512];
  sqlite3_stmt *stmt=NULL;
  int rc, i, t, node, count, done;

  printf("test_incoming_txn_visibility(nodes=%d txn_interval=%d block_interval=%d random_ports=%s)...",
         n, txn_interval, block_interval, bind_to_random_ports ? "yes" : "no"); fflush(stdout);

  assert(n>2 && n<512);

  /* delete the db files if they exist */
  delete_files(n);
  delete_log();

  /* generate private and public keys to manage the network */
  prepare_blockchain_admin_keys();

  /* open the connections to the databases using the admin public key */

  sprintf(uri, "file:db1.db?blockchain=on&bind=4301&discovery=127.0.0.1:4302&admin=%s&password=test&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[1])==SQLITE_OK );

  sprintf(uri, "file:db2.db?blockchain=on&bind=4302&discovery=127.0.0.1:4301&admin=%s&password=test&block_interval=%d", pkstr, block_interval);
  assert( sqlite3_open(uri, &db[2])==SQLITE_OK );

  for(i=3; i<=n; i++){
    if( bind_to_random_ports ){
      sprintf(uri, "file:db%d.db?blockchain=on&discovery=127.0.0.1:4301,127.0.0.1:4302&admin=%s&password=test&block_interval=%d", i, pkstr, block_interval);
    }else{
      sprintf(uri, "file:db%d.db?blockchain=on&bind=%d&discovery=127.0.0.1:4301,127.0.0.1:4302&admin=%s&password=test&block_interval=%d", i, 4300 + i, pkstr, block_interval);
    }
    //puts(uri);
    assert( sqlite3_open(uri, &db[i])==SQLITE_OK );
  }


  /* set the initial nonce value for each node */

  for(i=1; i<=n; i++){
    last_nonce[i] = 0;
  }

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* check the list of nodes connected to each of them */

  for(i=1; i<=n; i++){
    int nrows;
loc_again1:
    rc = sqlite3_prepare_v2(db[i], "pragma nodes", -1, &stmt, NULL);
    assert( rc==SQLITE_OK );
    assert( stmt!=NULL );

    // node_id | pubkey | type | address | CPU | OS | hostname | app | node_info |

    assert( sqlite3_column_count(stmt)==9 );
    nrows = 0;

    while( (rc=sqlite3_step(stmt))==SQLITE_ROW ){
      char *type = (char*)sqlite3_column_text(stmt, 2);
      assert( type && strcmp(type,"unauthorized")==0 );

      /* count how many peers this node is connected to */
      nrows++;

      /* the first item has info about the requester node */
      if( nrows==1 ){
        char *p = (char*)sqlite3_column_text(stmt, 1);
        assert( p && strlen(p)<sizeof(node_pubkey[1]) );
        strcpy(node_pubkey[i], p);
      }
    }
    assert( rc==SQLITE_DONE || rc==SQLITE_OK );
    sqlite3_finalize(stmt); stmt = NULL;

    if( (i<=2 && nrows<n) ||
        (i>2  && nrows<3) ){
      printf("."); fflush(stdout);
      sleep_ms(wait_time);
      goto loc_again1;
    }
  }

  puts("");


  /* register the callback function used to sign the admin transactions */

  for(i=1; i<=n; i++){
    sqlite3_create_function(db[i], "sign_transaction", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
      NULL, &on_sign_transaction, NULL, NULL);
  }


  /* add nodes to the network.
  ** the command is signed in the callback function, using the network admin's private key */

  int add_from_node = 1;
  int wait_between_add_nodes = 100;

  for(node=1; node<=n; node++){
    char cmd[128];
    printf("adding node %d to the network\n", node);
    //if( node_type_setting==USE_ADD_NODE && in_array_list(node,full_nodes) ){
    //  sprintf(cmd, "pragma add_node='full:%s'", node_pubkey[node]);
    //}else{
      sprintf(cmd, "pragma add_node='%s'", node_pubkey[node]);
    //}
    db_execute(db[add_from_node], cmd);
    sleep_ms(wait_between_add_nodes);
  }

  last_nonce[add_from_node] = n;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* checking if the node database is ready to execute transactions */

  for(i=1; i<=n; i++){
    printf("waiting until node %d is ready\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "PRAGMA db_is_ready");
      assert(rc==SQLITE_OK);
      done = (result==1);
    }
    assert(done);

  }


  /* create the test table on one of the nodes */

  printf("creating table...");

  assert( n>=4 );

  db_execute(db[3], "create table t1 (name)");
  db_execute(db[4], "create table t2 (name)");
  db_execute(db[2], "create table t3 (name)");

  last_nonce[3]++;
  last_nonce[4]++;
  last_nonce[2]++;

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  /* wait until the transactions are processed */

  for(i=1; i<=n; i++){

    printf("checking node %d\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from sqlite_master where name like 't%'");
      assert(rc==SQLITE_OK);
      done = (result==3);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1", 0);
    db_check_int(db[i], "select count(*) from t2", 0);
    db_check_int(db[i], "select count(*) from t3", 0);

  }

  /* check how many blocks until now */

  {
    char *str;
    db_query_str(&str, db[3], "pragma blockchain_status");
    puts(str);
    sqlite3_free(str);
  }

  /* test insertions at the same time on the same table */

  puts("inserting data on the same table...");

  int num_insertions = 7;

  for(t=1; t<=num_insertions; t++){
    printf("insertion batch # %d\n", t); fflush(stdout);
    for(i=1; i<=n; i++){
      char sql[256];
      //printf("executing on node %d\n", i); fflush(stdout);
      snprintf(sql, sizeof(sql), "insert into t1 values ('t%d i%d')", t, i);
      db_execute(db[i], sql);
      last_nonce[i]++;
    }
    sleep_ms(txn_interval);
  }

  for(i=1; i<=n; i++){
    db_check_int(db[i], "PRAGMA last_nonce", last_nonce[i]);
  }


  //puts("waiting...");
  //sleep_ms(block_interval * 2);


  /* check whether at least some data was replicated to all the nodes */

  for(i=1; i<=n; i++){
    int result;
    //char *str;

    printf("checking node %d\n", i); fflush(stdout);

    //db_query_str(&str, db[i], "pragma blockchain_status");
    //puts(str);
    //sqlite3_free(str);

    rc = db_query_int32(&result, db[i], "select count(*) from t1");
    assert(rc==SQLITE_OK);
    printf("# of rows: %d\n", result); fflush(stdout);
    assert(result>num_insertions);

    rc = db_query_int32(&result, db[i], "select count(*) from t1 where name like '%i1'");
    assert(rc==SQLITE_OK);
    assert(result>0);

    rc = db_query_int32(&result, db[i], "select count(*) from t1 where name like '%i2'");
    assert(rc==SQLITE_OK);
    assert(result>0);

    rc = db_query_int32(&result, db[i], "select count(*) from t1 where name like '%i3'");
    assert(rc==SQLITE_OK);
    assert(result>0);

    rc = db_query_int32(&result, db[i], "select count(*) from t1 where name like '%i4'");
    assert(rc==SQLITE_OK);
    assert(result>0);

  }

  /* wait until all the transactions are processed */

  int num_rows = n * num_insertions;

  for(i=1; i<=n; i++){

    printf("checking node %d\n", i); fflush(stdout);

    done = 0;
    for(count=0; !done && count<wait_repeat; count++){
      int result;
      if( count>0 ) sleep_ms(wait_time);
      rc = db_query_int32(&result, db[i], "select count(*) from t1");
      assert(rc==SQLITE_OK);
      done = (result==num_rows);
    }
    assert(done);

    db_check_int(db[i], "select count(*) from t1", num_rows);
    db_check_int(db[i], "select count(*) from t2", 0);
    db_check_int(db[i], "select count(*) from t3", 0);

  }

  for(i=1; i<=n; i++){
    sqlite3_close(db[i]);
  }

  secp256k1_context_destroy(ecdsa_ctx);

  puts("done");

}

/****************************************************************************/
/****************************************************************************/

int main(){

#ifndef _WIN32
  check_limit_of_open_files();
#endif

  sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, NULL);


//  test_5_nodes(0);
//  test_5_nodes(1);

//  test_n_nodes(10, true);
//  test_n_nodes(25, false);
//  test_n_nodes(50, true);
//  test_n_nodes(100, true);


  test_add_nodes(
    /* total nodes         */ 12,
    /* added at each time  */ 3,
    /* add from this node  */ 1,
    /* bind to random port */ true,
    /* block interval      */ BLOCK_INTERVAL);

  test_add_nodes(
    /* total nodes         */ 12,
    /* added at each time  */ 4,
    /* add from this node  */ 2,
    /* bind to random port */ true,
    /* block interval      */ BLOCK_INTERVAL);

  test_add_nodes(
    /* total nodes         */ 12,
    /* added at each time  */ 12,
    /* add from this node  */ 1,
    /* bind to random port */ true,
    /* block interval      */ BLOCK_INTERVAL);


  test_incoming_txn_visibility(
    /* total nodes         */ 5,
    /* txn interval        */ BLOCK_INTERVAL / 2,
    /* block interval      */ BLOCK_INTERVAL,
    /* bind to random port */ true);

  test_incoming_txn_visibility(
    /* total nodes         */ 5,
    /* txn interval        */ BLOCK_INTERVAL / 4,
    /* block interval      */ BLOCK_INTERVAL / 2,
    /* bind to random port */ true);


  test_invalid_admin(12, 1, true, BLOCK_INTERVAL);


  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 0,
    /* active_offline_nodes[],     */ (int[]){0},
    /* num_txns_on_online_nodes,   */ 0,
    /* active_online_nodes[],      */ (int[]){0},
    /* num_txns_on_reconnect,      */ 0,
    /* active_nodes_on_reconnect[] */ (int[]){0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 0,
    /* active_offline_nodes[],     */ (int[]){0},
    /* num_txns_on_online_nodes,   */ 0,
    /* active_online_nodes[],      */ (int[]){0},
    /* num_txns_on_reconnect,      */ 3,
    /* active_nodes_on_reconnect[] */ (int[]){3,6,9,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 0,
    /* active_offline_nodes[],     */ (int[]){0},
    /* num_txns_on_online_nodes,   */ 3,
    /* active_online_nodes[],      */ (int[]){3,8,0},
    /* num_txns_on_reconnect,      */ 0,
    /* active_nodes_on_reconnect[] */ (int[]){0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 0,
    /* active_offline_nodes[],     */ (int[]){0},
    /* num_txns_on_online_nodes,   */ 3,
    /* active_online_nodes[],      */ (int[]){3,8,0},
    /* num_txns_on_reconnect,      */ 5,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 3,
    /* active_offline_nodes[],     */ (int[]){4,10,0},
    /* num_txns_on_online_nodes,   */ 0,
    /* active_online_nodes[],      */ (int[]){0},
    /* num_txns_on_reconnect,      */ 0,
    /* active_nodes_on_reconnect[] */ (int[]){0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  /* all light nodes */
  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 3,
    /* active_offline_nodes[],     */ (int[]){4,10,0},
    /* num_txns_on_online_nodes,   */ 3,
    /* active_online_nodes[],      */ (int[]){3,8,0},
    /* num_txns_on_reconnect,      */ 5,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  /* all full nodes - using add_node */
  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){1,2,3,4,5,6,7,8,9,10,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 3,
    /* active_offline_nodes[],     */ (int[]){4,10,0},
    /* num_txns_on_online_nodes,   */ 3,
    /* active_online_nodes[],      */ (int[]){3,8,0},
    /* num_txns_on_reconnect,      */ 5,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  /* all full nodes - using node_type */
  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){1,2,3,4,5,6,7,8,9,10,0},
    /* node_type_setting,          */ USE_NODE_TYPE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 3,
    /* active_offline_nodes[],     */ (int[]){4,10,0},
    /* num_txns_on_online_nodes,   */ 3,
    /* active_online_nodes[],      */ (int[]){3,8,0},
    /* num_txns_on_reconnect,      */ 5,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  /* all full nodes - using node_type=* */
  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){1,2,3,4,5,6,7,8,9,10,0},
    /* node_type_setting,          */ USE_NODE_TYPE_ALL,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 3,
    /* active_offline_nodes[],     */ (int[]){4,10,0},
    /* num_txns_on_online_nodes,   */ 3,
    /* active_online_nodes[],      */ (int[]){3,8,0},
    /* num_txns_on_reconnect,      */ 5,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  /* some full nodes - using node_type */
  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){1,5,10,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 3,
    /* active_offline_nodes[],     */ (int[]){4,10,0},
    /* num_txns_on_online_nodes,   */ 3,
    /* active_online_nodes[],      */ (int[]){3,8,0},
    /* num_txns_on_reconnect,      */ 5,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  /* some full nodes - using node_type */
  test_reconnection(10, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){1,5,10,0},
    /* node_type_setting,          */ USE_NODE_TYPE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,0},
    /* num_txns_on_offline_nodes,  */ 3,
    /* active_offline_nodes[],     */ (int[]){4,10,0},
    /* num_txns_on_online_nodes,   */ 3,
    /* active_online_nodes[],      */ (int[]){3,8,0},
    /* num_txns_on_reconnect,      */ 5,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  test_reconnection(25, false, BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){2,10,20,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,15,20,23,0},
    /* num_txns_on_offline_nodes,  */ 6,
    /* active_offline_nodes[],     */ (int[]){2,7,15,23,0},
    /* num_txns_on_online_nodes,   */ 6,
    /* active_online_nodes[],      */ (int[]){3,8,11,17,0},
    /* num_txns_on_reconnect,      */ 9,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,21,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  test_reconnection(25, false, BLOCK_INTERVAL,   // majority disconnected
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,6,8,10,12,14,16,18,20,22,23,24,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){2,8,16,22,0},
    /* num_txns_on_online_nodes,   */ 6,
    /* active_online_nodes[],      */ (int[]){3,7,11,17,0},
    /* num_txns_on_reconnect,      */ 9,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,21,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(25, false, BLOCK_INTERVAL,   // all disconnected - node 2 reconnects first
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,1,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){1,5,15,17,23,0},
    /* num_txns_on_online_nodes,   */ 0,
    /* active_online_nodes[],      */ (int[]){0},
    /* num_txns_on_reconnect,      */ 9,
    /* active_nodes_on_reconnect[] */ (int[]){1,2,3,6,7,20,23,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(25, false, BLOCK_INTERVAL,   // all disconnected - main nodes reconnect later
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){1,5,15,17,23,0},
    /* num_txns_on_online_nodes,   */ 0,
    /* active_online_nodes[],      */ (int[]){0},
    /* num_txns_on_reconnect,      */ 9,
    /* active_nodes_on_reconnect[] */ (int[]){1,2,3,6,7,20,23,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(25, false, BLOCK_INTERVAL,   // all disconnected - main nodes reconnect in the middle
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){25,24,23,22,21,20,19,18,17,2,16,15,14,13,1,12,11,10,9,8,7,6,5,4,3,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){1,5,15,17,23,0},
    /* num_txns_on_online_nodes,   */ 0,
    /* active_online_nodes[],      */ (int[]){0},
    /* num_txns_on_reconnect,      */ 9,
    /* active_nodes_on_reconnect[] */ (int[]){1,2,3,6,7,20,23,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

#if 0
  test_reconnection(50, false, 2*BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,15,20,23,33,37,38,44,49,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,33,38,49,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,8,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,25,44,45,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  test_reconnection(50, false, 2*BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){1,10,20,30,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,15,20,23,33,37,38,44,49,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,33,38,49,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,8,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,25,44,45,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(50, false, 2*BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){1,10,20,30,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){49,44,38,37,33,23,20,15,10,7,4,2,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,33,38,49,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,8,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){45,44,25,20,7,6,3,2,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(50, false, 2*BLOCK_INTERVAL,  // majority disconnected
    /* full_nodes[]                */ (int[]){2,10,20,30,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){50,48,46,44,42,40,38,36,34,32,30,28,26,24,22,20,18,16,14,12,10,8,6,4,2,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,32,38,48,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,7,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){45,44,25,20,7,6,3,2,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(50, false, 2*BLOCK_INTERVAL,  // all disconnected
    /* full_nodes[]                */ (int[]){2,10,20,30,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,1,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,1,32,38,48,0},
    /* num_txns_on_online_nodes,   */ 0,
    /* active_online_nodes[],      */ (int[]){0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){45,44,25,20,7,6,3,2,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(50, false, 2*BLOCK_INTERVAL,
    /* full_nodes[]                */ (int[]){2,10,20,30,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){49,44,38,37,33,23,20,15,10,7,4,2,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){20,33,2,38,49,10,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){25,8,45,11,3,35,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){45,44,25,20,7,6,3,2,0},
    /* wait_between_add_nodes      */ 50,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(100, false, 3000,
    /* full_nodes[]                */ (int[]){2,27,55,99,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,15,20,23,33,37,38,44,49,55,66,77,88,95,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,33,38,49,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,8,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,25,44,45,0},
    /* wait_between_add_nodes      */ 250,
    /* check_connections           */ true,
    /* exec_while_adding           */ false
  );

  test_reconnection(100, false, 3000,
    /* full_nodes[]                */ (int[]){2,44,55,95,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,15,20,23,33,37,38,44,49,55,66,77,88,95,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,33,38,49,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,8,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,25,44,45,0},
    /* wait_between_add_nodes      */ 250,
    /* check_connections           */ false,
    /* exec_while_adding           */ false
  );

  test_reconnection(100, false, 3000,
    /* full_nodes[]                */ (int[]){2,44,55,95,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,15,20,23,33,37,38,44,49,55,66,77,88,95,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,33,38,49,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,8,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,25,44,45,0},
    /* wait_between_add_nodes      */ 250,
    /* check_connections           */ false,
    /* exec_while_adding           */ true
  );

  test_reconnection(150, false, 3000,
    /* full_nodes[]                */ (int[]){2,44,50,99,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,15,20,23,33,37,38,44,49,55,66,77,88,95,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,33,38,49,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,8,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,25,44,45,0},
    /* wait_between_add_nodes      */ 250,
    /* check_connections           */ false,
    /* exec_while_adding           */ true
   );

  test_reconnection(200, false, 5000,
    /* full_nodes[]                */ (int[]){2,95,150,199,0},
    /* node_type_setting,          */ USE_ADD_NODE,
    /* disconnect_nodes[]          */ (int[]){2,4,7,10,15,20,23,33,37,38,44,49,55,66,77,88,95,0},
    /* num_txns_on_offline_nodes,  */ 9,
    /* active_offline_nodes[],     */ (int[]){4,10,20,33,38,49,0},
    /* num_txns_on_online_nodes,   */ 9,
    /* active_online_nodes[],      */ (int[]){3,8,11,25,35,45,0},
    /* num_txns_on_reconnect,      */ 12,
    /* active_nodes_on_reconnect[] */ (int[]){2,3,6,7,20,25,44,45,0},
    /* wait_between_add_nodes      */ 250,
    /* check_connections           */ false,
    /* exec_while_adding           */ true
   );
#endif


//  test_new_nodes  -- some nodes join only after the majority already 'worked' for a while
    // join while other nodes are joining:
      // ones without content, others with offline content but first-time connection,
      // and others that were already part of the network but are now offline (without and with new offline content)
      // in 2 cases: the net has new blocks, the net does not have new block
    // join while the network was having less than majority
    // join while the network has less than majority, and keep having no majority

#if 0
  test_new_nodes(
    /* n_before,              */ 10,
    /* bind_to_random_ports,  */ true,
    /* block interval         */ BLOCK_INTERVAL,
    /* starting_node,         */ 2,
    /* n_new_nodes,           */ 2,
    /* n_old_no_content,      */ 1,
    /* n_old_with_content,    */ 2,
    /* new_blocks_on_net,     */ 1,
    /* num_offline_txns,      */ 3,

    /* n_remaining_online,    */ 12,
    /* n_new_nodes2           */ 0
  );
#endif


  test_new_nodes( /* total_nodes=14 */
    /* n_before,              */ 10,
    /* bind_to_random_ports,  */ true,
    /* block interval         */ BLOCK_INTERVAL,
    /* starting_node,         */ 2,
    /* n_new_nodes,           */ 2,
    /* n_old_no_content,      */ 1,
    /* n_old_with_content,    */ 1,
    /* new_blocks_on_net,     */ 1,
    /* num_offline_txns,      */ 3,

    /* n_remaining_online,    */ 6,
    /* n_new_nodes2           */ 2
  );

  test_new_nodes( /* total_nodes=14 */
    /* n_before,              */ 10,
    /* bind_to_random_ports,  */ true,
    /* block interval         */ BLOCK_INTERVAL,
    /* starting_node,         */ 2,
    /* n_new_nodes,           */ 2,
    /* n_old_no_content,      */ 1,
    /* n_old_with_content,    */ 1,
    /* new_blocks_on_net,     */ 5,
    /* num_offline_txns,      */ 3,

    /* n_remaining_online,    */ 6,
    /* n_new_nodes2           */ 2
  );


loc_exit:

  /* release global memory - to make valgrind happy */
  sqlite3_shutdown();

  /* delete the test db files on success */
  delete_files(200);
  delete_log();

  puts("OK. All tests pass!"); return 0;
}
