Skip to content

Verifying data immutability in Insolar MainNet

Olle Solens edited this page Jul 14, 2020 · 4 revisions

With the help of this document, you’ll learn how to check that transaction data in Insolar MainNet remains immutable with time.

In particular, you’ll learn:

  • What can be considered a “block” in Insolar MainNet
  • How to:
    • Request “block” contents both from MainNet and from your local node
    • Compare both data sets, save the results
    • Request the same data later at any moment and compare again to ensure the “block” has not changed

Pulses as immutable information units

Insolar MainNet works in 10-second cycles—pulses. During each pulse, the network agrees on entropy (random number) and the list of nodes that reach consensus and may participate in the network, for example, perform computations. Pulses are numbered sequentially and provide an agreed-upon timestamp.

Any transaction processed by the Insolar MainNet, for example, a transfer between user wallets, is registered in a specific pulse. Once registered in a pulse, they remain there immutably as data entries. Thus, a pulse is analogous to a “block” in a traditional blockchain.

Transaction structure

A transaction as returned by Insolar’s API is registered in a pulse. Transactions have sets of attributes that depend on the transaction type. For example, an XNS transfer from one user to another is as follows:

{
  "amount":"4173200000000",
  "fee":"100000000",
  "index":"48077096:8",
  "pulseNumber":48077096,
  "status":"received",
  "timestamp":1594312359,
  "txID":"insolar:1At2ZKAtN69KeTB5zHfDuLX71NRgBaDrrRTitHp5RjDY.record",
  "fromMemberReference":"insolar:1AtzCRgk3QxOBgqVhcDQQvIruLu5K2B2Oqhb7-OQBT-Q",
  "toMemberReference":"insolar:1Ag6rgI9GWg8ODZ0Mf3At8ehc1HYQUFbivJFkCVGn59s",
  "type":"transfer"
}

There are 3 types of transactions:

  • migration—when a user sends INS tokens to their migration address in the Ethereum network, the MainNet observes the Ethereum transaction and sends an equivalent amount of XNS coins to the user’s deposit account
  • release—when a sufficient amount of XNS coins is available to the user according to the vesting scheme, they can be “released” from the user’s deposit account to the user’s current account
  • transfer—when a user sends (“transfers”) XNS coins to another user

Since all transactions of different types essentially transfer XNS coins to and from different MainNet objects, transactions themselves contain references to those objects and are referenced by them.

Any transaction triggers, first, the balance decrease of the sender object, then the balance increase of the receiver object. The fee value is also subtracted from the sender’s balance but only after the transaction is registered in the pulse.

All registered transactions remain in their pulse, so pulse contents do not change.

However, some transaction attributes that depend on other objects in the ledger may change their value. Those attributes are limited to the following ones:

  • status:
    • First, a transaction isregistered
    • Then, the coins are sent by the sender
    • Lastly, the coins are received by the recipient
  • fee (only for transfer transactions):
    • first, 0 when the transaction is registered
    • then, 100000000 when the transaction is successfully executed
  • In release transactions, the fromDepositReference is "" upon registration, then it changes to the actual Insolar reference with the format of insolar:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

All attributes of all new transactions transition to their final values during several pulses. If you request transactions from already fully persisted (“historical”) pulses, you can expect transaction attribute values to remain unchanged in their final state.

All the transactions comprising the pulse and their attributes except listed above are immutable.

Verifying pulse immutability

You can check transactions in pulses in the following way:

  1. Compare pulse contents in MainNet and on your own local node.
  2. Save both the request as you formed it and the received response(s).
  3. Request the same data from MainNet (and your local node) periodically and compare it again with the response you saved for as many times as required.

You will find no difference between the contents of fully persisted pulses in MainNet and your local node.

Requesting contents of a pulse (or a pulse range)

You can request transactions within a pulse range both from the MainNet Observer’s and your node’s APIs.

This request has 3 required query parameters:

  • fromPulseNumber—starts the pulse number range (inclusive)
  • toPulseNumber—ends the pulse number range (inclusive)
  • limit—to the number of transactions in response

The range is specified inclusively, so you can request transactions from a single pulse and limit lets you control the number of returned transactions as there may be many.

For example, request with the following parameter vaues:

https://wallet-api.insolar.io/api/transactions/inPulseNumberRange?limit=1&fromPulseNumber=48077096&toPulseNumber=48077096

Always returns:

[
  {
     "amount":"4173200000000",
     "fee":"100000000",
     "index":"48077096:8",
     "pulseNumber":48077096,
     "status":"received",
     "timestamp":1594312359,
     "txID":"insolar:1At2ZKAtN69KeTB5zHfDuLX71NRgBaDrrRTitHp5RjDY.record",
     "fromMemberReference":"insolar:1AtzCRgk3QxOBgqVhcDQQvIruLu5K2B2Oqhb7-OQBT-Q",
     "toMemberReference":"insolar:1Ag6rgI9GWg8ODZ0Mf3At8ehc1HYQUFbivJFkCVGn59s",
     "type":"transfer"
  }
]

Comparing pulse contents automatically

First, pick a pulse number range in the past. You can find pulse numbers via Insolar Explorer or by sending a corresponding API request.

Then consider a simple Golang script listed below. Is does the following:

  • Declares constants for the API invocation: URLs and the endpoint for getting transactions from a pulse range
  • Defines a helper function to request the data from the API
  • Forms identical requests and invokes both the MainNet and your node’s APIs
  • Compares the results
  • Saves both the formed requests and the received results

With the data that this script saves, you can continue requesting the same pulse contents from MainNet and comparing them to the ones you confirmed identical on your node.

Here’s the code sample:

 package main
 import (
   "encoding/json"
   "fmt"
   "io/ioutil"
   "net/http"
   "net/url"
   "os"
   "time"
   // The following package provides simplistic plain text diff representation
   // The diff lines designated with - and +
   df "github.com/kylelemons/godebug/diff"
 )
  const (
    MainNetURL = "https://wallet-api.insolar.io/api"
    Endpoint = "/transactions/inPulseNumberRange"
    // Your node’s API URL as you configured it:
    YourNodeURL = "http://127.0.0.1:8080/api"
  )
 func sendRequest(URLandEndpoint string, params url.Values) (string, string) {

   // Initialize the client
   client := http.Client{}

   // Create a new GET request
   transactionsReq, err := http.NewRequest("GET", URLandEndpoint, nil)
   if err != nil {
      fmt.Println(err)
      os.Exit(1)
   }
   // Add query params
   transactionsReq.URL.RawQuery = params.Encode()
   fmt.Println("GET " + transactionsReq.URL.String())

   // Send the request
   transactionsResp, err := client.Do(transactionsReq)
   if err != nil {
      fmt.Println(err)
      os.Exit(1)
   }
   defer transactionsResp.Body.Close()

   // Read the response body
   transactionsRespBody, _ := ioutil.ReadAll(transactionsResp.Body)
   fmt.Println("Response status: " + transactionsResp.Status)

   // Unmarshal the response into a JSON
   var rawBody []interface{}
   err = json.Unmarshal(transactionsRespBody, &rawBody)
   if err != nil {
      fmt.Println(err)
      os.Exit(1)
   }

   // Prettify the JSON: add indentation for better human-readability
   prettyRespJSON, err := json.MarshalIndent(rawBody, "", "  ")
   if err != nil {
      fmt.Println(err)
      os.Exit(1)
   }

   // Return both the response as it is returned by the API (a one-line string)
   // and the beautiful JSON for pretty-printing
   return string(transactionsRespBody), string(prettyRespJSON)
 }

 func main() {
   // Set query parameters for the transactions-in-pulse-number-range request
   params:= url.Values{}
   params.Add("fromPulseNumber", "48077096")
   params.Add("toPulseNumber", "48077096")
   params.Add("limit", "10")

    // Save the request string to a file to be able to copy-paste and send the exact same request later
   // To do so, first, create a file and put the current date and time into its name
   currentTime := time.Now().Format(time.UnixDate)
   file, err := os.Create( currentTime + ".txt")
   if err != nil {
      fmt.Println(err)
      return
   }
   // Then write the request URL with all query parameters to it
   requestString := MainNetURL + Endpoint + "?" + params.Encode()
   file.WriteString("The request is as follows:\n\n")
   file.WriteString(requestString + "\n\n")

   // Send the request to MainNet using the sender function declared earlier and receive pulse contents
   fmt.Println("\nGetting transactions within a pulse number range from MainNet with the following request:")
   mainNetPulseContent, prettyMainNetPulseContent := sendRequest(MainNetURL + Endpoint, params)

   // Send the exact same request to your node and receive pulse contents
   fmt.Println("\nGetting transactions within a pulse number range locally with the following request:")
   nodePulseContent, prettyNodePulseContent := sendRequest(YourNodeURL + Endpoint, params)

   // Compare the contents with basic Golang comparison operator (==)
   if mainNetPulseContent == nodePulseContent {
      // This is the case with identical pulse contents from both MainNet and your node

      // Write the pulse contents to the file to be able to later compare it to the results of the exact same request
      file.WriteString("Pulse contents from MainNet and your node are identical. Here's the response:\n\n")
      file.WriteString(mainNetPulseContent)
      file.Close()
      fmt.Println("\nThe request and result it returned are saved to the \"" + currentTime + ".txt\" file.")

   } else {
      // This is the case with pulse contents that differ
      // Remember: some transaction attributes may change their values since the values depend on separate objects
      // But each pulse always contains the same set of registered transactions

      // Form a simple plain text diff between prettified response—JSON object with indentation
      indentedDiff := df.Diff(prettyMainNetPulseContent, prettyNodePulseContent)

      // Write the diff to the file
      file.WriteString("The diff goes as follows:\n\n")
      file.WriteString(indentedDiff)
      file.Close()
      fmt.Println("\nThe request and diff are saved to the \"" + currentTime + ".txt\" file.")
   }
 }

Summary

Using the above simple script, you can:

  • Request the contents of the same pulse from both MainNet and your node
  • Compare them and confirm that they are identical
  • Save both the request and pulse contents to a file
  • Send an identical request to the MainNet later at any point
  • Compare again the pulse contents to the ones you saved and, again, confirm that they remain the same