Skip to content

Utilities

Karl Oczadly edited this page Feb 12, 2021 · 52 revisions

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

The above mentioned block classes all offer a range of constructors to create and populate the relevant fields. In addition, the StateBlockBuilder utility class can be used to build instances of StateBlock classes, accepting a variety of different arguments. If constructing from a JSON block, you can call the Block.parse(String) static method.

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 can 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, and will instead use the account value.

StateBlock block = new StateBlockBuilder(StateBlockSubType.OPEN)
        .setLink("BF4A559FEF44D4A9C9CEF4972886A51FC83AD1A2BEE4CDD732F62F3C166D6D4F")
        .setBalance("123000000000000000000000000")
        .generateWork(yourWorkGenerator) // Read the below section on work generation
        .buildAndSign("A3293644AC105DEE5A0202B7EF976A06E790908EE0E8CC43AEF845380BFF954E"); // Private key

Which creates a block with the equivalent JSON: (output generated by calling 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 device (eg. GPU).
May not be supported for all devices and operating systems.
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.
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)
Future<GeneratedWork> result = workGen.generate(block); // Request work generation
GeneratedWork work = result.get();                      // Blocks the thread until completed

// 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());

Sample console output:

Work       = b36f1c81ef4b84b8
Difficulty = ffffffa57889f40b
Multiplier = x5.656

Accounts

Nano accounts can be represented by the NanoAccount class, which allows you to split 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
Clone this wiki locally