Skip to content

Utilities

Karl Oczadly edited this page Mar 29, 2021 · 52 revisions

Table of contents

Blocks

Blocks can be represented by the built-in model classes: StateBlock, OpenBlock, ChangeBlock, SendBlock, ReceiveBlock. All of these implementation classes extend the abstract Block class.

Creation & construction

Parsing JSON objects

To parse a block from it's JSON contents (either a Gson JsonObject or a String), you can make use of the Block.parse(String) static method. If you know the type ahead of time, then you could call the method on the appropriate block class (eg. StateBlock.parse(...)) which will automatically validate and cast the object for you.

Manual construction using a builder

The StateBlockBuilder builder class may be used to manually construct (and sign) blocks through the use of setter methods.

The following example will create a state block from a set of information, and sign the block. The account field does not need to be specified as it will be derived from the private key, and the previous field is not set as the block is an open subtype. The representative field may also be optionally ignored, which will instead use its own account as the representative.

StateBlock block = StateBlock.builder()
        .subtype(StateBlockSubType.OPEN)
        .link("BF4A559FEF44D4A9C9CEF4972886A51FC83AD1A2BEE4CDD732F62F3C166D6D4F")
        .balance("123000000000000000000000000")
        .generateWork(yourWorkGenerator) // Read the below section on work generation
        .buildAndSign("A3293644AC105DEE5A0202B7EF976A06E790908EE0E8CC43AEF845380BFF954E"); // Private key

Which creates a block with the equivalent JSON (output generated from toString()):

{
  "account": "nano_1x87c399bbijjzi849nmdtoz7f1soa7tdcbp31dzsfmu6y7zfn4tp8wizrjb",
  "previous": "0000000000000000000000000000000000000000000000000000000000000000",
  "representative": "nano_1x87c399bbijjzi849nmdtoz7f1soa7tdcbp31dzsfmu6y7zfn4tp8wizrjb",
  "balance": "123000000000000000000000000",
  "subtype": "open",
  "type": "state",
  "signature": "26B8EBAFE951FEFB94305E1890DC3E82B8E0E42FB1F0341C3AA752CCA01C846888B69564037052DE404A9240CB94E1D5E8C6A5100EC48549194B683E56449603",
  "work": "994efa0f9d8f702d",
  "link": "BF4A559FEF44D4A9C9CEF4972886A51FC83AD1A2BEE4CDD732F62F3C166D6D4F",
  "link_as_account": "nano_3htccphyyj8no96wxx6q745cc9ya9dat7hq6sqdm7xjh9id8tuchag7zcw19"
}

Hashing

Blocks can be directly hashed without the use of an external node or tool through the getHash() method of the Block classes. Simply construct an instance through one of the above methods, then call getHash() on the block to calculate and retrieve the hash.

Signing

Blocks may be signed using the sign(HexData) instance method by passing the private key of the signer account. The signature of the block will be overwritten with the new computed signature value. If you're creating a new block, simply pass a null or placeholder value as the signature during construction, and then call the sign() method on the block with your private key.

The signature value may also be verified through the verifySignature(NanoAccount) method, which tests if the provided account (public key) matches the signature value and block contents.

Alternatively, the static methods offered by CryptoUtil may be used to perform more low-level signature operations, though these aren't recommended unless necessary.

Proof of work

Proof of work solutions can be represented by the WorkSolution class, and work difficulties can be represented by the WorkDifficulty class. Both of these model classes offer a range of methods to run direct calculations, without requiring a connection to a node.

Difficulty thresholds

The WorkDifficultyPolicy interface can be used to fetch the minimum work difficulty threshold for blocks. The following implementations are available:

Implementation Description
NodeWorkDifficultyPolicy Retrieves the recommended difficulties from an external node through the RPC interface.
ConstantDifficultyPolicy Uses the same difficulty for all block types.
ConstantDifficultyPolicyV2 Uses two sets of difficulties for send and receive block subtypes.
BoundedWorkDifficultyPolicy Wraps a WorkDifficultyPolicy and restricts the multiplier to a specified range.

You can also retrieve the constant values from the NetworkConstants class like so:

WorkDifficultyPolicy nano = NetworkConstants.NANO.getWorkDifficulties();
WorkDifficultyPolicy banano = NetworkConstants.BANANO.getWorkDifficulties();

Work generation

The WorkGenerator implementation classes may be used to generate proof-of-work solutions for blocks. The following implementations are provided:

Implementation Description
OpenCLWorkGenerator Generates work on an OpenCL compatible device (eg. GPU).
May not be supported for all devices and operating systems. This is usually faster than the default nano-node work generation implementation.
CPUWorkGenerator Generates work on the CPU within the JVM.
This method may be inefficient, but is a reliable source of work generation.
NodeWorkGenerator Generates work on an external node through the RPC interface.
CombinedWorkGenerator Combines multiple WorkGenerator objects to compute in parallel.
Eg: multiple work peers, multiple GPU devices, CPU and GPU.
DPOWWorkGenerator Generates work using the DPoW or BoomPow external services. You will need to request access to use these services.

Important: You should aim to reduce the number of instantiated WorkGenerator objects in your application as they spawn a background thread. Instances should be re-used throughout your application.

Example work generation:

// Declare your work generator (read above, only create one instance!)
WorkGenerator workGen = new CPUWorkGenerator();

// Generate the work
Block block = ...  // (Your block)
FutureWork result = workGen.generate(block); // Request work generation

try {
    GeneratedWork work = result.get(); // Fetch the work (will block the thread until completion)
    
    // Print the result
    System.out.printf("Work       = %s%n",    work.getWork());
    System.out.printf("Difficulty = %s%n",    work.getDifficulty());
    System.out.printf("Multiplier = x%.3f%n", work.getMultiplier());
} catch (ExecutionException | InterruptedException e) {
    // Work could not be generated
    System.err.println("Could not generate work");
}

Sample console output:

Work       = b36f1c81ef4b84b8
Difficulty = ffffffa57889f40b
Multiplier = x5.656

Accounts

Nano accounts can be represented by the NanoAccount class, which allows you to parse and convert the address between different formats. This class supports any prefix, as long as the format and checksum computation remains the same as the official Nano network.

Construction

To construct this class, use one of the available static parsing methods. For most use cases, the parse(String) function would be appropriate. Other parse methods are available for parsing and verifying specific formats. Constructors are also offered for creating an account from a given array of bytes, or for changing the protocol identifier prefix.

The following code will parse an address, and obtain some values:

NanoAccount account = NanoAccount.parse("nano_34qjpc8t1u6wnb584pc4iwsukwa8jhrobpx4oea5gbaitnqafm6qsgoacpiz");
account.toPublicKey(); // "8AF1B28DA06C9CA2466159428733B971068BF154DBA2AB10372510D52E86CC97"
account.getPrefix();   // "nano"
account.isValidNano(); // true (if prefix is either "nano" or "xrb")

Validation

If you wish to validate an address, static methods are also provided. isValidNano(String) will check that an account's format and checksum is valid and ensure that the prefix is either nano or xrb. The isValid(String, String...) method will also check validity of the address, and compare against the array of allowed prefixes (if provided).

String address = "ban_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94xr";
NanoAccount.isValid(address, "ban");  // Returns true
NanoAccount.isValid(address, "nano"); // Returns false (prefix doesn't match)
NanoAccount.isValidNano(address);     // Returns false (prefix must be 'nano' or 'xrb')

Deriving from a private key

To obtain the account (public key) of a private key, you can use the fromPrivateKey​(HexData) static method. For example:

HexData privateKey = new HexData("80BC56D9D0260915F7967F5891C7DB41EF1B680842E521474933C5DA6102A089");
NanoAccount account = NanoAccount.fromPrivateKey(privateKey);
String addr = account.toAddress(); // nano_36zya77ueshnu1n5pge6nxo63xrx6a9c8kcp3uk8byo9br67rubphu5b4o14

Wallets

The WalletUtil provides various utility methods for creating your own wallets. generateRandomKey() can be used to securely generate a new random seed or private key.

For creating private keys from a seed, the deriveKeyFromSeed(HexData, int) method can be used. This is compatible with the Nano reference wallet, and uses the same algorithm to derive the private key from the seed. For example:

HexData seed = new HexData("D59B918B46835FD053A316E6DEE19AC0CA536641CF8CFBEC1184843E8765D79E");
HexData pk1 = WalletUtil.deriveKeyFromSeed(seed, 0); // 928A53D4CADA117A17A5FD10BEF9FF537E7CD8D27CA09D7180CCE13B6E486077
HexData pk2 = WalletUtil.deriveKeyFromSeed(seed, 1); // 90F2F3E35F657ED0F8E7F8C85DA07D757F899212A43C3FE082620B5FEC5630CC
HexData pk3 = WalletUtil.deriveKeyFromSeed(seed, 2); // F5AC271B8BB50DEE4A3C6AA9FD57104B9E98E404FE188290E199528D08AA6DFC

Unit conversion

For representing quantities of the currency and performing unit conversions, the NanoAmount class can be used. A range of static methods are provided to parse an amount of Nano (valueOfNano(BigDecimal)) or Raw (valueOfRaw(BigInteger)) into an object. The class provides various instance methods for formatting and converting to different types.

The following example will convert Nano into raw:

NanoAmount amount = NanoAmount.valueOfNano("27.4");

String str = amount.toString();     // "27.4 NANO"
BigInteger raw = amount.getAsRaw(); // 27400000000000000000000000000000

Constants

The NetworkConstants class provides a range of different static final constants which provide access to miscellaneous values for different networks and forks.

The following data is made available:

  • Genesis block / account
  • Work difficulty thresholds
  • Account prefix
  • Account epoch upgrades