-
Notifications
You must be signed in to change notification settings - Fork 1
Verifying data immutability in Insolar MainNet
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
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.
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 is
registered
- Then, the coins are
sent
by the sender - Lastly, the coins are
received
by the recipient
- First, a transaction is
-
fee
(only fortransfer
transactions):- first,
0
when the transaction isregistered
- then,
100000000
when the transaction is successfully executed
- first,
- In
release
transactions, thefromDepositReference
is""
upon registration, then it changes to the actual Insolar reference with the format ofinsolar: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.
You can check transactions in pulses in the following way:
- Compare pulse contents in MainNet and on your own local node.
- Save both the request as you formed it and the received response(s).
- 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.
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"
}
]
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.")
}
}
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