-
Notifications
You must be signed in to change notification settings - Fork 6
Utilities
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.
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.
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"
}
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.
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 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.
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();
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
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.
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")
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')
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
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
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
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