diff --git a/TestNets.md b/TestNets.md index e475e593..b4b9feed 100644 --- a/TestNets.md +++ b/TestNets.md @@ -52,14 +52,13 @@ ## Single-node testnet -A single-node testnet is possible with code modifications, for basic testing, or to more easily start a new testnet. -To do so, follow these steps: -- Comment out the `if (mintedLastBlock) { }` conditional in BlockMinter.java -- Comment out the `minBlockchainPeers` validation in Settings.validate() -- Set `minBlockchainPeers` to 0 in settings.json -- Set `Synchronizer.RECOVERY_MODE_TIMEOUT` to `0` -- All other steps should remain the same. Only a single reward share key is needed. -- Remember to put these values back after introducing other nodes +A single-node testnet is possible with an additional settings, or to more easily start a new testnet. +Just add this setting: +``` +"singleNodeTestnet": true +``` +This will automatically allow multiple consecutive blocks to be minted, as well as setting minBlockchainPeers to 0. +Remember to put these values back after introducing other nodes ## Fixed network @@ -93,3 +92,32 @@ Your options are: - `qort` tool, but prepend with one-time shell variable: `BASE_URL=some-node-hostname-or-ip:port qort ......` - `peer-heights`, but use `-t` option, or `BASE_URL` shell variable as above +## Example settings-test.json +``` +{ + "isTestNet": true, + "bitcoinNet": "TEST3", + "repositoryPath": "db-testnet", + "blockchainConfig": "testchain.json", + "minBlockchainPeers": 1, + "apiDocumentationEnabled": true, + "apiRestricted": false, + "bootstrap": false, + "maxPeerConnectionTime": 999999999, + "localAuthBypassEnabled": true, + "singleNodeTestnet": true, + "recoveryModeTimeout": 0 +} +``` + +## Quick start +Here are some steps to quickly get a single node testnet up and running with a generic minting account: +1. Start with template `settings-test.json`, and create a `testchain.json` based on mainnet's blockchain.json (or obtain one from Qortal developers). These should be in the same directory as the jar. +2. Make sure feature triggers and other timestamp/height activations are correctly set. Generally these would be `0` so that they are enabled from the start. +3. Set a recent genesis `timestamp` in testchain.json, and add this reward share entry: +`{ "type": "REWARD_SHARE", "minterPublicKey": "DwcUnhxjamqppgfXCLgbYRx8H9XFPUc2qYRy3CEvQWEw", "recipient": "QbTDMss7NtRxxQaSqBZtSLSNdSYgvGaqFf", "rewardSharePublicKey": "CRvQXxFfUMfr4q3o1PcUZPA4aPCiubBsXkk47GzRo754", "sharePercent": 0 },` +4. Start the node, passing in settings-test.json, e.g: `java -jar qortal.jar settings-test.json` +5. Once started, add the corresponding minting key to the node: +`curl -X POST "http://localhost:62391/admin/mintingaccounts" -d "F48mYJycFgRdqtc58kiovwbcJgVukjzRE4qRRtRsK9ix"` +6. Alternatively you can use your own minting account instead of the generic one above. +7. After a short while, blocks should be minted from the genesis timestamp until the current time. \ No newline at end of file diff --git a/WindowsInstaller/Qortal.aip b/WindowsInstaller/Qortal.aip index e0ce06b0..7af02485 100755 --- a/WindowsInstaller/Qortal.aip +++ b/WindowsInstaller/Qortal.aip @@ -17,10 +17,10 @@ - + - + @@ -212,7 +212,7 @@ - + @@ -1173,7 +1173,7 @@ - + diff --git a/lib/org/ciyam/AT/1.4.0/AT-1.4.0.jar b/lib/org/ciyam/AT/1.4.0/AT-1.4.0.jar new file mode 100644 index 00000000..c2c3d355 Binary files /dev/null and b/lib/org/ciyam/AT/1.4.0/AT-1.4.0.jar differ diff --git a/lib/org/ciyam/AT/1.4.0/AT-1.4.0.pom b/lib/org/ciyam/AT/1.4.0/AT-1.4.0.pom new file mode 100644 index 00000000..0dc1aedc --- /dev/null +++ b/lib/org/ciyam/AT/1.4.0/AT-1.4.0.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + org.ciyam + AT + 1.4.0 + POM was created from install:install-file + diff --git a/lib/org/ciyam/AT/maven-metadata-local.xml b/lib/org/ciyam/AT/maven-metadata-local.xml index 8f8b1f6e..063c735d 100644 --- a/lib/org/ciyam/AT/maven-metadata-local.xml +++ b/lib/org/ciyam/AT/maven-metadata-local.xml @@ -3,14 +3,15 @@ org.ciyam AT - 1.3.8 + 1.4.0 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 + 1.4.0 - 20200925114415 + 20221105114346 diff --git a/pom.xml b/pom.xml index 85bb2a0c..12f8472c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,15 +3,15 @@ 4.0.0 org.qortal qortal - 3.3.7 + 3.8.4 jar true - 6628cfd + 7dc8c6f 0.15.10 - 1.64 + 1.69 ${maven.build.timestamp} - 1.3.8 + 1.4.0 3.6 1.8 2.6 @@ -34,6 +34,8 @@ 1.1.0 1.13.1 4.10 + 1.45.1 + 3.19.4 src/main/java @@ -705,5 +707,25 @@ java-diff-utils ${java-diff-utils.version} + + io.grpc + grpc-netty + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + com.google.protobuf + protobuf-java + ${protobuf.version} + diff --git a/src/main/java/cash/z/wallet/sdk/rpc/CompactFormats.java b/src/main/java/cash/z/wallet/sdk/rpc/CompactFormats.java new file mode 100644 index 00000000..7ab80569 --- /dev/null +++ b/src/main/java/cash/z/wallet/sdk/rpc/CompactFormats.java @@ -0,0 +1,4499 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: compact_formats.proto + +package cash.z.wallet.sdk.rpc; + +public final class CompactFormats { + private CompactFormats() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + public interface CompactBlockOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.CompactBlock) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * the version of this wire format, for storage
+     * 
+ * + * uint32 protoVersion = 1; + * @return The protoVersion. + */ + int getProtoVersion(); + + /** + *
+     * the height of this block
+     * 
+ * + * uint64 height = 2; + * @return The height. + */ + long getHeight(); + + /** + *
+     * the ID (hash) of this block, same as in block explorers
+     * 
+ * + * bytes hash = 3; + * @return The hash. + */ + com.google.protobuf.ByteString getHash(); + + /** + *
+     * the ID (hash) of this block's predecessor
+     * 
+ * + * bytes prevHash = 4; + * @return The prevHash. + */ + com.google.protobuf.ByteString getPrevHash(); + + /** + *
+     * Unix epoch time when the block was mined
+     * 
+ * + * uint32 time = 5; + * @return The time. + */ + int getTime(); + + /** + *
+     * (hash, prevHash, and time) OR (full header)
+     * 
+ * + * bytes header = 6; + * @return The header. + */ + com.google.protobuf.ByteString getHeader(); + + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + java.util.List + getVtxList(); + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx getVtx(int index); + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + int getVtxCount(); + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + java.util.List + getVtxOrBuilderList(); + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + cash.z.wallet.sdk.rpc.CompactFormats.CompactTxOrBuilder getVtxOrBuilder( + int index); + } + /** + *
+   * CompactBlock is a packaging of ONLY the data from a block that's needed to:
+   *   1. Detect a payment to your shielded Sapling address
+   *   2. Detect a spend of your shielded Sapling notes
+   *   3. Update your witnesses to generate new Sapling spend proofs.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.CompactBlock} + */ + public static final class CompactBlock extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.CompactBlock) + CompactBlockOrBuilder { + private static final long serialVersionUID = 0L; + // Use CompactBlock.newBuilder() to construct. + private CompactBlock(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private CompactBlock() { + hash_ = com.google.protobuf.ByteString.EMPTY; + prevHash_ = com.google.protobuf.ByteString.EMPTY; + header_ = com.google.protobuf.ByteString.EMPTY; + vtx_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new CompactBlock(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private CompactBlock( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + protoVersion_ = input.readUInt32(); + break; + } + case 16: { + + height_ = input.readUInt64(); + break; + } + case 26: { + + hash_ = input.readBytes(); + break; + } + case 34: { + + prevHash_ = input.readBytes(); + break; + } + case 40: { + + time_ = input.readUInt32(); + break; + } + case 50: { + + header_ = input.readBytes(); + break; + } + case 58: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + vtx_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + vtx_.add( + input.readMessage(cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + vtx_ = java.util.Collections.unmodifiableList(vtx_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactBlock_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactBlock_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.class, cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.Builder.class); + } + + public static final int PROTOVERSION_FIELD_NUMBER = 1; + private int protoVersion_; + /** + *
+     * the version of this wire format, for storage
+     * 
+ * + * uint32 protoVersion = 1; + * @return The protoVersion. + */ + @java.lang.Override + public int getProtoVersion() { + return protoVersion_; + } + + public static final int HEIGHT_FIELD_NUMBER = 2; + private long height_; + /** + *
+     * the height of this block
+     * 
+ * + * uint64 height = 2; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + + public static final int HASH_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString hash_; + /** + *
+     * the ID (hash) of this block, same as in block explorers
+     * 
+ * + * bytes hash = 3; + * @return The hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHash() { + return hash_; + } + + public static final int PREVHASH_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString prevHash_; + /** + *
+     * the ID (hash) of this block's predecessor
+     * 
+ * + * bytes prevHash = 4; + * @return The prevHash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getPrevHash() { + return prevHash_; + } + + public static final int TIME_FIELD_NUMBER = 5; + private int time_; + /** + *
+     * Unix epoch time when the block was mined
+     * 
+ * + * uint32 time = 5; + * @return The time. + */ + @java.lang.Override + public int getTime() { + return time_; + } + + public static final int HEADER_FIELD_NUMBER = 6; + private com.google.protobuf.ByteString header_; + /** + *
+     * (hash, prevHash, and time) OR (full header)
+     * 
+ * + * bytes header = 6; + * @return The header. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHeader() { + return header_; + } + + public static final int VTX_FIELD_NUMBER = 7; + private java.util.List vtx_; + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + @java.lang.Override + public java.util.List getVtxList() { + return vtx_; + } + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + @java.lang.Override + public java.util.List + getVtxOrBuilderList() { + return vtx_; + } + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + @java.lang.Override + public int getVtxCount() { + return vtx_.size(); + } + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx getVtx(int index) { + return vtx_.get(index); + } + /** + *
+     * zero or more compact transactions from this block
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTxOrBuilder getVtxOrBuilder( + int index) { + return vtx_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (protoVersion_ != 0) { + output.writeUInt32(1, protoVersion_); + } + if (height_ != 0L) { + output.writeUInt64(2, height_); + } + if (!hash_.isEmpty()) { + output.writeBytes(3, hash_); + } + if (!prevHash_.isEmpty()) { + output.writeBytes(4, prevHash_); + } + if (time_ != 0) { + output.writeUInt32(5, time_); + } + if (!header_.isEmpty()) { + output.writeBytes(6, header_); + } + for (int i = 0; i < vtx_.size(); i++) { + output.writeMessage(7, vtx_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (protoVersion_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(1, protoVersion_); + } + if (height_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(2, height_); + } + if (!hash_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, hash_); + } + if (!prevHash_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, prevHash_); + } + if (time_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(5, time_); + } + if (!header_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(6, header_); + } + for (int i = 0; i < vtx_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(7, vtx_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock other = (cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock) obj; + + if (getProtoVersion() + != other.getProtoVersion()) return false; + if (getHeight() + != other.getHeight()) return false; + if (!getHash() + .equals(other.getHash())) return false; + if (!getPrevHash() + .equals(other.getPrevHash())) return false; + if (getTime() + != other.getTime()) return false; + if (!getHeader() + .equals(other.getHeader())) return false; + if (!getVtxList() + .equals(other.getVtxList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + PROTOVERSION_FIELD_NUMBER; + hash = (53 * hash) + getProtoVersion(); + hash = (37 * hash) + HEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getHeight()); + hash = (37 * hash) + HASH_FIELD_NUMBER; + hash = (53 * hash) + getHash().hashCode(); + hash = (37 * hash) + PREVHASH_FIELD_NUMBER; + hash = (53 * hash) + getPrevHash().hashCode(); + hash = (37 * hash) + TIME_FIELD_NUMBER; + hash = (53 * hash) + getTime(); + hash = (37 * hash) + HEADER_FIELD_NUMBER; + hash = (53 * hash) + getHeader().hashCode(); + if (getVtxCount() > 0) { + hash = (37 * hash) + VTX_FIELD_NUMBER; + hash = (53 * hash) + getVtxList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * CompactBlock is a packaging of ONLY the data from a block that's needed to:
+     *   1. Detect a payment to your shielded Sapling address
+     *   2. Detect a spend of your shielded Sapling notes
+     *   3. Update your witnesses to generate new Sapling spend proofs.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.CompactBlock} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.CompactBlock) + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlockOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactBlock_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactBlock_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.class, cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getVtxFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + protoVersion_ = 0; + + height_ = 0L; + + hash_ = com.google.protobuf.ByteString.EMPTY; + + prevHash_ = com.google.protobuf.ByteString.EMPTY; + + time_ = 0; + + header_ = com.google.protobuf.ByteString.EMPTY; + + if (vtxBuilder_ == null) { + vtx_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + vtxBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactBlock_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock build() { + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock buildPartial() { + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock result = new cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock(this); + int from_bitField0_ = bitField0_; + result.protoVersion_ = protoVersion_; + result.height_ = height_; + result.hash_ = hash_; + result.prevHash_ = prevHash_; + result.time_ = time_; + result.header_ = header_; + if (vtxBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + vtx_ = java.util.Collections.unmodifiableList(vtx_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.vtx_ = vtx_; + } else { + result.vtx_ = vtxBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock) { + return mergeFrom((cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock other) { + if (other == cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.getDefaultInstance()) return this; + if (other.getProtoVersion() != 0) { + setProtoVersion(other.getProtoVersion()); + } + if (other.getHeight() != 0L) { + setHeight(other.getHeight()); + } + if (other.getHash() != com.google.protobuf.ByteString.EMPTY) { + setHash(other.getHash()); + } + if (other.getPrevHash() != com.google.protobuf.ByteString.EMPTY) { + setPrevHash(other.getPrevHash()); + } + if (other.getTime() != 0) { + setTime(other.getTime()); + } + if (other.getHeader() != com.google.protobuf.ByteString.EMPTY) { + setHeader(other.getHeader()); + } + if (vtxBuilder_ == null) { + if (!other.vtx_.isEmpty()) { + if (vtx_.isEmpty()) { + vtx_ = other.vtx_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureVtxIsMutable(); + vtx_.addAll(other.vtx_); + } + onChanged(); + } + } else { + if (!other.vtx_.isEmpty()) { + if (vtxBuilder_.isEmpty()) { + vtxBuilder_.dispose(); + vtxBuilder_ = null; + vtx_ = other.vtx_; + bitField0_ = (bitField0_ & ~0x00000001); + vtxBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getVtxFieldBuilder() : null; + } else { + vtxBuilder_.addAllMessages(other.vtx_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private int protoVersion_ ; + /** + *
+       * the version of this wire format, for storage
+       * 
+ * + * uint32 protoVersion = 1; + * @return The protoVersion. + */ + @java.lang.Override + public int getProtoVersion() { + return protoVersion_; + } + /** + *
+       * the version of this wire format, for storage
+       * 
+ * + * uint32 protoVersion = 1; + * @param value The protoVersion to set. + * @return This builder for chaining. + */ + public Builder setProtoVersion(int value) { + + protoVersion_ = value; + onChanged(); + return this; + } + /** + *
+       * the version of this wire format, for storage
+       * 
+ * + * uint32 protoVersion = 1; + * @return This builder for chaining. + */ + public Builder clearProtoVersion() { + + protoVersion_ = 0; + onChanged(); + return this; + } + + private long height_ ; + /** + *
+       * the height of this block
+       * 
+ * + * uint64 height = 2; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + /** + *
+       * the height of this block
+       * 
+ * + * uint64 height = 2; + * @param value The height to set. + * @return This builder for chaining. + */ + public Builder setHeight(long value) { + + height_ = value; + onChanged(); + return this; + } + /** + *
+       * the height of this block
+       * 
+ * + * uint64 height = 2; + * @return This builder for chaining. + */ + public Builder clearHeight() { + + height_ = 0L; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString hash_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * the ID (hash) of this block, same as in block explorers
+       * 
+ * + * bytes hash = 3; + * @return The hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHash() { + return hash_; + } + /** + *
+       * the ID (hash) of this block, same as in block explorers
+       * 
+ * + * bytes hash = 3; + * @param value The hash to set. + * @return This builder for chaining. + */ + public Builder setHash(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + hash_ = value; + onChanged(); + return this; + } + /** + *
+       * the ID (hash) of this block, same as in block explorers
+       * 
+ * + * bytes hash = 3; + * @return This builder for chaining. + */ + public Builder clearHash() { + + hash_ = getDefaultInstance().getHash(); + onChanged(); + return this; + } + + private com.google.protobuf.ByteString prevHash_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * the ID (hash) of this block's predecessor
+       * 
+ * + * bytes prevHash = 4; + * @return The prevHash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getPrevHash() { + return prevHash_; + } + /** + *
+       * the ID (hash) of this block's predecessor
+       * 
+ * + * bytes prevHash = 4; + * @param value The prevHash to set. + * @return This builder for chaining. + */ + public Builder setPrevHash(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + prevHash_ = value; + onChanged(); + return this; + } + /** + *
+       * the ID (hash) of this block's predecessor
+       * 
+ * + * bytes prevHash = 4; + * @return This builder for chaining. + */ + public Builder clearPrevHash() { + + prevHash_ = getDefaultInstance().getPrevHash(); + onChanged(); + return this; + } + + private int time_ ; + /** + *
+       * Unix epoch time when the block was mined
+       * 
+ * + * uint32 time = 5; + * @return The time. + */ + @java.lang.Override + public int getTime() { + return time_; + } + /** + *
+       * Unix epoch time when the block was mined
+       * 
+ * + * uint32 time = 5; + * @param value The time to set. + * @return This builder for chaining. + */ + public Builder setTime(int value) { + + time_ = value; + onChanged(); + return this; + } + /** + *
+       * Unix epoch time when the block was mined
+       * 
+ * + * uint32 time = 5; + * @return This builder for chaining. + */ + public Builder clearTime() { + + time_ = 0; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString header_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * (hash, prevHash, and time) OR (full header)
+       * 
+ * + * bytes header = 6; + * @return The header. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHeader() { + return header_; + } + /** + *
+       * (hash, prevHash, and time) OR (full header)
+       * 
+ * + * bytes header = 6; + * @param value The header to set. + * @return This builder for chaining. + */ + public Builder setHeader(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + header_ = value; + onChanged(); + return this; + } + /** + *
+       * (hash, prevHash, and time) OR (full header)
+       * 
+ * + * bytes header = 6; + * @return This builder for chaining. + */ + public Builder clearHeader() { + + header_ = getDefaultInstance().getHeader(); + onChanged(); + return this; + } + + private java.util.List vtx_ = + java.util.Collections.emptyList(); + private void ensureVtxIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + vtx_ = new java.util.ArrayList(vtx_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactTxOrBuilder> vtxBuilder_; + + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public java.util.List getVtxList() { + if (vtxBuilder_ == null) { + return java.util.Collections.unmodifiableList(vtx_); + } else { + return vtxBuilder_.getMessageList(); + } + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public int getVtxCount() { + if (vtxBuilder_ == null) { + return vtx_.size(); + } else { + return vtxBuilder_.getCount(); + } + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx getVtx(int index) { + if (vtxBuilder_ == null) { + return vtx_.get(index); + } else { + return vtxBuilder_.getMessage(index); + } + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder setVtx( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx value) { + if (vtxBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureVtxIsMutable(); + vtx_.set(index, value); + onChanged(); + } else { + vtxBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder setVtx( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder builderForValue) { + if (vtxBuilder_ == null) { + ensureVtxIsMutable(); + vtx_.set(index, builderForValue.build()); + onChanged(); + } else { + vtxBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder addVtx(cash.z.wallet.sdk.rpc.CompactFormats.CompactTx value) { + if (vtxBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureVtxIsMutable(); + vtx_.add(value); + onChanged(); + } else { + vtxBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder addVtx( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx value) { + if (vtxBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureVtxIsMutable(); + vtx_.add(index, value); + onChanged(); + } else { + vtxBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder addVtx( + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder builderForValue) { + if (vtxBuilder_ == null) { + ensureVtxIsMutable(); + vtx_.add(builderForValue.build()); + onChanged(); + } else { + vtxBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder addVtx( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder builderForValue) { + if (vtxBuilder_ == null) { + ensureVtxIsMutable(); + vtx_.add(index, builderForValue.build()); + onChanged(); + } else { + vtxBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder addAllVtx( + java.lang.Iterable values) { + if (vtxBuilder_ == null) { + ensureVtxIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, vtx_); + onChanged(); + } else { + vtxBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder clearVtx() { + if (vtxBuilder_ == null) { + vtx_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + vtxBuilder_.clear(); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public Builder removeVtx(int index) { + if (vtxBuilder_ == null) { + ensureVtxIsMutable(); + vtx_.remove(index); + onChanged(); + } else { + vtxBuilder_.remove(index); + } + return this; + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder getVtxBuilder( + int index) { + return getVtxFieldBuilder().getBuilder(index); + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTxOrBuilder getVtxOrBuilder( + int index) { + if (vtxBuilder_ == null) { + return vtx_.get(index); } else { + return vtxBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public java.util.List + getVtxOrBuilderList() { + if (vtxBuilder_ != null) { + return vtxBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(vtx_); + } + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder addVtxBuilder() { + return getVtxFieldBuilder().addBuilder( + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.getDefaultInstance()); + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder addVtxBuilder( + int index) { + return getVtxFieldBuilder().addBuilder( + index, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.getDefaultInstance()); + } + /** + *
+       * zero or more compact transactions from this block
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactTx vtx = 7; + */ + public java.util.List + getVtxBuilderList() { + return getVtxFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactTxOrBuilder> + getVtxFieldBuilder() { + if (vtxBuilder_ == null) { + vtxBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactTxOrBuilder>( + vtx_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + vtx_ = null; + } + return vtxBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.CompactBlock) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.CompactBlock) + private static final cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock(); + } + + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public CompactBlock parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new CompactBlock(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface CompactTxOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.CompactTx) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * the index within the full block
+     * 
+ * + * uint64 index = 1; + * @return The index. + */ + long getIndex(); + + /** + *
+     * the ID (hash) of this transaction, same as in block explorers
+     * 
+ * + * bytes hash = 2; + * @return The hash. + */ + com.google.protobuf.ByteString getHash(); + + /** + *
+     * The transaction fee: present if server can provide. In the case of a
+     * stateless server and a transaction with transparent inputs, this will be
+     * unset because the calculation requires reference to prior transactions.
+     * in a pure-Sapling context, the fee will be calculable as:
+     *    valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
+     * 
+ * + * uint32 fee = 3; + * @return The fee. + */ + int getFee(); + + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + java.util.List + getSpendsList(); + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend getSpends(int index); + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + int getSpendsCount(); + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + java.util.List + getSpendsOrBuilderList(); + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpendOrBuilder getSpendsOrBuilder( + int index); + + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + java.util.List + getOutputsList(); + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput getOutputs(int index); + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + int getOutputsCount(); + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + java.util.List + getOutputsOrBuilderList(); + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutputOrBuilder getOutputsOrBuilder( + int index); + } + /** + *
+   * CompactTx contains the minimum information for a wallet to know if this transaction
+   * is relevant to it (either pays to it or spends from it) via shielded elements
+   * only. This message will not encode a transparent-to-transparent transaction.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.CompactTx} + */ + public static final class CompactTx extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.CompactTx) + CompactTxOrBuilder { + private static final long serialVersionUID = 0L; + // Use CompactTx.newBuilder() to construct. + private CompactTx(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private CompactTx() { + hash_ = com.google.protobuf.ByteString.EMPTY; + spends_ = java.util.Collections.emptyList(); + outputs_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new CompactTx(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private CompactTx( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + index_ = input.readUInt64(); + break; + } + case 18: { + + hash_ = input.readBytes(); + break; + } + case 24: { + + fee_ = input.readUInt32(); + break; + } + case 34: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + spends_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + spends_.add( + input.readMessage(cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.parser(), extensionRegistry)); + break; + } + case 42: { + if (!((mutable_bitField0_ & 0x00000002) != 0)) { + outputs_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000002; + } + outputs_.add( + input.readMessage(cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + spends_ = java.util.Collections.unmodifiableList(spends_); + } + if (((mutable_bitField0_ & 0x00000002) != 0)) { + outputs_ = java.util.Collections.unmodifiableList(outputs_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactTx_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactTx_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.class, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder.class); + } + + public static final int INDEX_FIELD_NUMBER = 1; + private long index_; + /** + *
+     * the index within the full block
+     * 
+ * + * uint64 index = 1; + * @return The index. + */ + @java.lang.Override + public long getIndex() { + return index_; + } + + public static final int HASH_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString hash_; + /** + *
+     * the ID (hash) of this transaction, same as in block explorers
+     * 
+ * + * bytes hash = 2; + * @return The hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHash() { + return hash_; + } + + public static final int FEE_FIELD_NUMBER = 3; + private int fee_; + /** + *
+     * The transaction fee: present if server can provide. In the case of a
+     * stateless server and a transaction with transparent inputs, this will be
+     * unset because the calculation requires reference to prior transactions.
+     * in a pure-Sapling context, the fee will be calculable as:
+     *    valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
+     * 
+ * + * uint32 fee = 3; + * @return The fee. + */ + @java.lang.Override + public int getFee() { + return fee_; + } + + public static final int SPENDS_FIELD_NUMBER = 4; + private java.util.List spends_; + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + @java.lang.Override + public java.util.List getSpendsList() { + return spends_; + } + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + @java.lang.Override + public java.util.List + getSpendsOrBuilderList() { + return spends_; + } + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + @java.lang.Override + public int getSpendsCount() { + return spends_.size(); + } + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend getSpends(int index) { + return spends_.get(index); + } + /** + *
+     * inputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpendOrBuilder getSpendsOrBuilder( + int index) { + return spends_.get(index); + } + + public static final int OUTPUTS_FIELD_NUMBER = 5; + private java.util.List outputs_; + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + @java.lang.Override + public java.util.List getOutputsList() { + return outputs_; + } + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + @java.lang.Override + public java.util.List + getOutputsOrBuilderList() { + return outputs_; + } + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + @java.lang.Override + public int getOutputsCount() { + return outputs_.size(); + } + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput getOutputs(int index) { + return outputs_.get(index); + } + /** + *
+     * outputs
+     * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutputOrBuilder getOutputsOrBuilder( + int index) { + return outputs_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (index_ != 0L) { + output.writeUInt64(1, index_); + } + if (!hash_.isEmpty()) { + output.writeBytes(2, hash_); + } + if (fee_ != 0) { + output.writeUInt32(3, fee_); + } + for (int i = 0; i < spends_.size(); i++) { + output.writeMessage(4, spends_.get(i)); + } + for (int i = 0; i < outputs_.size(); i++) { + output.writeMessage(5, outputs_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (index_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(1, index_); + } + if (!hash_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, hash_); + } + if (fee_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(3, fee_); + } + for (int i = 0; i < spends_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(4, spends_.get(i)); + } + for (int i = 0; i < outputs_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(5, outputs_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.CompactFormats.CompactTx)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx other = (cash.z.wallet.sdk.rpc.CompactFormats.CompactTx) obj; + + if (getIndex() + != other.getIndex()) return false; + if (!getHash() + .equals(other.getHash())) return false; + if (getFee() + != other.getFee()) return false; + if (!getSpendsList() + .equals(other.getSpendsList())) return false; + if (!getOutputsList() + .equals(other.getOutputsList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + INDEX_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getIndex()); + hash = (37 * hash) + HASH_FIELD_NUMBER; + hash = (53 * hash) + getHash().hashCode(); + hash = (37 * hash) + FEE_FIELD_NUMBER; + hash = (53 * hash) + getFee(); + if (getSpendsCount() > 0) { + hash = (37 * hash) + SPENDS_FIELD_NUMBER; + hash = (53 * hash) + getSpendsList().hashCode(); + } + if (getOutputsCount() > 0) { + hash = (37 * hash) + OUTPUTS_FIELD_NUMBER; + hash = (53 * hash) + getOutputsList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.CompactFormats.CompactTx prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * CompactTx contains the minimum information for a wallet to know if this transaction
+     * is relevant to it (either pays to it or spends from it) via shielded elements
+     * only. This message will not encode a transparent-to-transparent transaction.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.CompactTx} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.CompactTx) + cash.z.wallet.sdk.rpc.CompactFormats.CompactTxOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactTx_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactTx_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.class, cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getSpendsFieldBuilder(); + getOutputsFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + index_ = 0L; + + hash_ = com.google.protobuf.ByteString.EMPTY; + + fee_ = 0; + + if (spendsBuilder_ == null) { + spends_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + spendsBuilder_.clear(); + } + if (outputsBuilder_ == null) { + outputs_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + } else { + outputsBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactTx_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx build() { + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx buildPartial() { + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx result = new cash.z.wallet.sdk.rpc.CompactFormats.CompactTx(this); + int from_bitField0_ = bitField0_; + result.index_ = index_; + result.hash_ = hash_; + result.fee_ = fee_; + if (spendsBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + spends_ = java.util.Collections.unmodifiableList(spends_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.spends_ = spends_; + } else { + result.spends_ = spendsBuilder_.build(); + } + if (outputsBuilder_ == null) { + if (((bitField0_ & 0x00000002) != 0)) { + outputs_ = java.util.Collections.unmodifiableList(outputs_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.outputs_ = outputs_; + } else { + result.outputs_ = outputsBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.CompactFormats.CompactTx) { + return mergeFrom((cash.z.wallet.sdk.rpc.CompactFormats.CompactTx)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.CompactFormats.CompactTx other) { + if (other == cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.getDefaultInstance()) return this; + if (other.getIndex() != 0L) { + setIndex(other.getIndex()); + } + if (other.getHash() != com.google.protobuf.ByteString.EMPTY) { + setHash(other.getHash()); + } + if (other.getFee() != 0) { + setFee(other.getFee()); + } + if (spendsBuilder_ == null) { + if (!other.spends_.isEmpty()) { + if (spends_.isEmpty()) { + spends_ = other.spends_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureSpendsIsMutable(); + spends_.addAll(other.spends_); + } + onChanged(); + } + } else { + if (!other.spends_.isEmpty()) { + if (spendsBuilder_.isEmpty()) { + spendsBuilder_.dispose(); + spendsBuilder_ = null; + spends_ = other.spends_; + bitField0_ = (bitField0_ & ~0x00000001); + spendsBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getSpendsFieldBuilder() : null; + } else { + spendsBuilder_.addAllMessages(other.spends_); + } + } + } + if (outputsBuilder_ == null) { + if (!other.outputs_.isEmpty()) { + if (outputs_.isEmpty()) { + outputs_ = other.outputs_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureOutputsIsMutable(); + outputs_.addAll(other.outputs_); + } + onChanged(); + } + } else { + if (!other.outputs_.isEmpty()) { + if (outputsBuilder_.isEmpty()) { + outputsBuilder_.dispose(); + outputsBuilder_ = null; + outputs_ = other.outputs_; + bitField0_ = (bitField0_ & ~0x00000002); + outputsBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getOutputsFieldBuilder() : null; + } else { + outputsBuilder_.addAllMessages(other.outputs_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.CompactFormats.CompactTx) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private long index_ ; + /** + *
+       * the index within the full block
+       * 
+ * + * uint64 index = 1; + * @return The index. + */ + @java.lang.Override + public long getIndex() { + return index_; + } + /** + *
+       * the index within the full block
+       * 
+ * + * uint64 index = 1; + * @param value The index to set. + * @return This builder for chaining. + */ + public Builder setIndex(long value) { + + index_ = value; + onChanged(); + return this; + } + /** + *
+       * the index within the full block
+       * 
+ * + * uint64 index = 1; + * @return This builder for chaining. + */ + public Builder clearIndex() { + + index_ = 0L; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString hash_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * the ID (hash) of this transaction, same as in block explorers
+       * 
+ * + * bytes hash = 2; + * @return The hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHash() { + return hash_; + } + /** + *
+       * the ID (hash) of this transaction, same as in block explorers
+       * 
+ * + * bytes hash = 2; + * @param value The hash to set. + * @return This builder for chaining. + */ + public Builder setHash(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + hash_ = value; + onChanged(); + return this; + } + /** + *
+       * the ID (hash) of this transaction, same as in block explorers
+       * 
+ * + * bytes hash = 2; + * @return This builder for chaining. + */ + public Builder clearHash() { + + hash_ = getDefaultInstance().getHash(); + onChanged(); + return this; + } + + private int fee_ ; + /** + *
+       * The transaction fee: present if server can provide. In the case of a
+       * stateless server and a transaction with transparent inputs, this will be
+       * unset because the calculation requires reference to prior transactions.
+       * in a pure-Sapling context, the fee will be calculable as:
+       *    valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
+       * 
+ * + * uint32 fee = 3; + * @return The fee. + */ + @java.lang.Override + public int getFee() { + return fee_; + } + /** + *
+       * The transaction fee: present if server can provide. In the case of a
+       * stateless server and a transaction with transparent inputs, this will be
+       * unset because the calculation requires reference to prior transactions.
+       * in a pure-Sapling context, the fee will be calculable as:
+       *    valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
+       * 
+ * + * uint32 fee = 3; + * @param value The fee to set. + * @return This builder for chaining. + */ + public Builder setFee(int value) { + + fee_ = value; + onChanged(); + return this; + } + /** + *
+       * The transaction fee: present if server can provide. In the case of a
+       * stateless server and a transaction with transparent inputs, this will be
+       * unset because the calculation requires reference to prior transactions.
+       * in a pure-Sapling context, the fee will be calculable as:
+       *    valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
+       * 
+ * + * uint32 fee = 3; + * @return This builder for chaining. + */ + public Builder clearFee() { + + fee_ = 0; + onChanged(); + return this; + } + + private java.util.List spends_ = + java.util.Collections.emptyList(); + private void ensureSpendsIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + spends_ = new java.util.ArrayList(spends_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpendOrBuilder> spendsBuilder_; + + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public java.util.List getSpendsList() { + if (spendsBuilder_ == null) { + return java.util.Collections.unmodifiableList(spends_); + } else { + return spendsBuilder_.getMessageList(); + } + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public int getSpendsCount() { + if (spendsBuilder_ == null) { + return spends_.size(); + } else { + return spendsBuilder_.getCount(); + } + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend getSpends(int index) { + if (spendsBuilder_ == null) { + return spends_.get(index); + } else { + return spendsBuilder_.getMessage(index); + } + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder setSpends( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend value) { + if (spendsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSpendsIsMutable(); + spends_.set(index, value); + onChanged(); + } else { + spendsBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder setSpends( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder builderForValue) { + if (spendsBuilder_ == null) { + ensureSpendsIsMutable(); + spends_.set(index, builderForValue.build()); + onChanged(); + } else { + spendsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder addSpends(cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend value) { + if (spendsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSpendsIsMutable(); + spends_.add(value); + onChanged(); + } else { + spendsBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder addSpends( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend value) { + if (spendsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSpendsIsMutable(); + spends_.add(index, value); + onChanged(); + } else { + spendsBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder addSpends( + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder builderForValue) { + if (spendsBuilder_ == null) { + ensureSpendsIsMutable(); + spends_.add(builderForValue.build()); + onChanged(); + } else { + spendsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder addSpends( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder builderForValue) { + if (spendsBuilder_ == null) { + ensureSpendsIsMutable(); + spends_.add(index, builderForValue.build()); + onChanged(); + } else { + spendsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder addAllSpends( + java.lang.Iterable values) { + if (spendsBuilder_ == null) { + ensureSpendsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, spends_); + onChanged(); + } else { + spendsBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder clearSpends() { + if (spendsBuilder_ == null) { + spends_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + spendsBuilder_.clear(); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public Builder removeSpends(int index) { + if (spendsBuilder_ == null) { + ensureSpendsIsMutable(); + spends_.remove(index); + onChanged(); + } else { + spendsBuilder_.remove(index); + } + return this; + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder getSpendsBuilder( + int index) { + return getSpendsFieldBuilder().getBuilder(index); + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpendOrBuilder getSpendsOrBuilder( + int index) { + if (spendsBuilder_ == null) { + return spends_.get(index); } else { + return spendsBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public java.util.List + getSpendsOrBuilderList() { + if (spendsBuilder_ != null) { + return spendsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(spends_); + } + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder addSpendsBuilder() { + return getSpendsFieldBuilder().addBuilder( + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.getDefaultInstance()); + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder addSpendsBuilder( + int index) { + return getSpendsFieldBuilder().addBuilder( + index, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.getDefaultInstance()); + } + /** + *
+       * inputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactSpend spends = 4; + */ + public java.util.List + getSpendsBuilderList() { + return getSpendsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpendOrBuilder> + getSpendsFieldBuilder() { + if (spendsBuilder_ == null) { + spendsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpendOrBuilder>( + spends_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + spends_ = null; + } + return spendsBuilder_; + } + + private java.util.List outputs_ = + java.util.Collections.emptyList(); + private void ensureOutputsIsMutable() { + if (!((bitField0_ & 0x00000002) != 0)) { + outputs_ = new java.util.ArrayList(outputs_); + bitField0_ |= 0x00000002; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutputOrBuilder> outputsBuilder_; + + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public java.util.List getOutputsList() { + if (outputsBuilder_ == null) { + return java.util.Collections.unmodifiableList(outputs_); + } else { + return outputsBuilder_.getMessageList(); + } + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public int getOutputsCount() { + if (outputsBuilder_ == null) { + return outputs_.size(); + } else { + return outputsBuilder_.getCount(); + } + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput getOutputs(int index) { + if (outputsBuilder_ == null) { + return outputs_.get(index); + } else { + return outputsBuilder_.getMessage(index); + } + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder setOutputs( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput value) { + if (outputsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureOutputsIsMutable(); + outputs_.set(index, value); + onChanged(); + } else { + outputsBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder setOutputs( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder builderForValue) { + if (outputsBuilder_ == null) { + ensureOutputsIsMutable(); + outputs_.set(index, builderForValue.build()); + onChanged(); + } else { + outputsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder addOutputs(cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput value) { + if (outputsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureOutputsIsMutable(); + outputs_.add(value); + onChanged(); + } else { + outputsBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder addOutputs( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput value) { + if (outputsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureOutputsIsMutable(); + outputs_.add(index, value); + onChanged(); + } else { + outputsBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder addOutputs( + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder builderForValue) { + if (outputsBuilder_ == null) { + ensureOutputsIsMutable(); + outputs_.add(builderForValue.build()); + onChanged(); + } else { + outputsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder addOutputs( + int index, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder builderForValue) { + if (outputsBuilder_ == null) { + ensureOutputsIsMutable(); + outputs_.add(index, builderForValue.build()); + onChanged(); + } else { + outputsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder addAllOutputs( + java.lang.Iterable values) { + if (outputsBuilder_ == null) { + ensureOutputsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, outputs_); + onChanged(); + } else { + outputsBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder clearOutputs() { + if (outputsBuilder_ == null) { + outputs_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + } else { + outputsBuilder_.clear(); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public Builder removeOutputs(int index) { + if (outputsBuilder_ == null) { + ensureOutputsIsMutable(); + outputs_.remove(index); + onChanged(); + } else { + outputsBuilder_.remove(index); + } + return this; + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder getOutputsBuilder( + int index) { + return getOutputsFieldBuilder().getBuilder(index); + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutputOrBuilder getOutputsOrBuilder( + int index) { + if (outputsBuilder_ == null) { + return outputs_.get(index); } else { + return outputsBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public java.util.List + getOutputsOrBuilderList() { + if (outputsBuilder_ != null) { + return outputsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(outputs_); + } + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder addOutputsBuilder() { + return getOutputsFieldBuilder().addBuilder( + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.getDefaultInstance()); + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder addOutputsBuilder( + int index) { + return getOutputsFieldBuilder().addBuilder( + index, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.getDefaultInstance()); + } + /** + *
+       * outputs
+       * 
+ * + * repeated .cash.z.wallet.sdk.rpc.CompactOutput outputs = 5; + */ + public java.util.List + getOutputsBuilderList() { + return getOutputsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutputOrBuilder> + getOutputsFieldBuilder() { + if (outputsBuilder_ == null) { + outputsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutputOrBuilder>( + outputs_, + ((bitField0_ & 0x00000002) != 0), + getParentForChildren(), + isClean()); + outputs_ = null; + } + return outputsBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.CompactTx) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.CompactTx) + private static final cash.z.wallet.sdk.rpc.CompactFormats.CompactTx DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.CompactFormats.CompactTx(); + } + + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactTx getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public CompactTx parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new CompactTx(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactTx getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface CompactSpendOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.CompactSpend) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * nullifier (see the Zcash protocol specification)
+     * 
+ * + * bytes nf = 1; + * @return The nf. + */ + com.google.protobuf.ByteString getNf(); + } + /** + *
+   * CompactSpend is a Sapling Spend Description as described in 7.3 of the Zcash
+   * protocol specification.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.CompactSpend} + */ + public static final class CompactSpend extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.CompactSpend) + CompactSpendOrBuilder { + private static final long serialVersionUID = 0L; + // Use CompactSpend.newBuilder() to construct. + private CompactSpend(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private CompactSpend() { + nf_ = com.google.protobuf.ByteString.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new CompactSpend(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private CompactSpend( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + + nf_ = input.readBytes(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactSpend_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactSpend_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.class, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder.class); + } + + public static final int NF_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString nf_; + /** + *
+     * nullifier (see the Zcash protocol specification)
+     * 
+ * + * bytes nf = 1; + * @return The nf. + */ + @java.lang.Override + public com.google.protobuf.ByteString getNf() { + return nf_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!nf_.isEmpty()) { + output.writeBytes(1, nf_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!nf_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, nf_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend other = (cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend) obj; + + if (!getNf() + .equals(other.getNf())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + NF_FIELD_NUMBER; + hash = (53 * hash) + getNf().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * CompactSpend is a Sapling Spend Description as described in 7.3 of the Zcash
+     * protocol specification.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.CompactSpend} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.CompactSpend) + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpendOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactSpend_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactSpend_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.class, cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + nf_ = com.google.protobuf.ByteString.EMPTY; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactSpend_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend build() { + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend buildPartial() { + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend result = new cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend(this); + result.nf_ = nf_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend) { + return mergeFrom((cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend other) { + if (other == cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend.getDefaultInstance()) return this; + if (other.getNf() != com.google.protobuf.ByteString.EMPTY) { + setNf(other.getNf()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private com.google.protobuf.ByteString nf_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * nullifier (see the Zcash protocol specification)
+       * 
+ * + * bytes nf = 1; + * @return The nf. + */ + @java.lang.Override + public com.google.protobuf.ByteString getNf() { + return nf_; + } + /** + *
+       * nullifier (see the Zcash protocol specification)
+       * 
+ * + * bytes nf = 1; + * @param value The nf to set. + * @return This builder for chaining. + */ + public Builder setNf(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + nf_ = value; + onChanged(); + return this; + } + /** + *
+       * nullifier (see the Zcash protocol specification)
+       * 
+ * + * bytes nf = 1; + * @return This builder for chaining. + */ + public Builder clearNf() { + + nf_ = getDefaultInstance().getNf(); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.CompactSpend) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.CompactSpend) + private static final cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend(); + } + + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public CompactSpend parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new CompactSpend(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactSpend getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface CompactOutputOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.CompactOutput) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * note commitment u-coordinate
+     * 
+ * + * bytes cmu = 1; + * @return The cmu. + */ + com.google.protobuf.ByteString getCmu(); + + /** + *
+     * ephemeral public key
+     * 
+ * + * bytes epk = 2; + * @return The epk. + */ + com.google.protobuf.ByteString getEpk(); + + /** + *
+     * ciphertext and zkproof
+     * 
+ * + * bytes ciphertext = 3; + * @return The ciphertext. + */ + com.google.protobuf.ByteString getCiphertext(); + } + /** + *
+   * output is a Sapling Output Description as described in section 7.4 of the
+   * Zcash protocol spec. Total size is 948.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.CompactOutput} + */ + public static final class CompactOutput extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.CompactOutput) + CompactOutputOrBuilder { + private static final long serialVersionUID = 0L; + // Use CompactOutput.newBuilder() to construct. + private CompactOutput(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private CompactOutput() { + cmu_ = com.google.protobuf.ByteString.EMPTY; + epk_ = com.google.protobuf.ByteString.EMPTY; + ciphertext_ = com.google.protobuf.ByteString.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new CompactOutput(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private CompactOutput( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + + cmu_ = input.readBytes(); + break; + } + case 18: { + + epk_ = input.readBytes(); + break; + } + case 26: { + + ciphertext_ = input.readBytes(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactOutput_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactOutput_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.class, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder.class); + } + + public static final int CMU_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString cmu_; + /** + *
+     * note commitment u-coordinate
+     * 
+ * + * bytes cmu = 1; + * @return The cmu. + */ + @java.lang.Override + public com.google.protobuf.ByteString getCmu() { + return cmu_; + } + + public static final int EPK_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString epk_; + /** + *
+     * ephemeral public key
+     * 
+ * + * bytes epk = 2; + * @return The epk. + */ + @java.lang.Override + public com.google.protobuf.ByteString getEpk() { + return epk_; + } + + public static final int CIPHERTEXT_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString ciphertext_; + /** + *
+     * ciphertext and zkproof
+     * 
+ * + * bytes ciphertext = 3; + * @return The ciphertext. + */ + @java.lang.Override + public com.google.protobuf.ByteString getCiphertext() { + return ciphertext_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!cmu_.isEmpty()) { + output.writeBytes(1, cmu_); + } + if (!epk_.isEmpty()) { + output.writeBytes(2, epk_); + } + if (!ciphertext_.isEmpty()) { + output.writeBytes(3, ciphertext_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!cmu_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, cmu_); + } + if (!epk_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, epk_); + } + if (!ciphertext_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, ciphertext_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput other = (cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput) obj; + + if (!getCmu() + .equals(other.getCmu())) return false; + if (!getEpk() + .equals(other.getEpk())) return false; + if (!getCiphertext() + .equals(other.getCiphertext())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + CMU_FIELD_NUMBER; + hash = (53 * hash) + getCmu().hashCode(); + hash = (37 * hash) + EPK_FIELD_NUMBER; + hash = (53 * hash) + getEpk().hashCode(); + hash = (37 * hash) + CIPHERTEXT_FIELD_NUMBER; + hash = (53 * hash) + getCiphertext().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * output is a Sapling Output Description as described in section 7.4 of the
+     * Zcash protocol spec. Total size is 948.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.CompactOutput} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.CompactOutput) + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutputOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactOutput_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactOutput_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.class, cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + cmu_ = com.google.protobuf.ByteString.EMPTY; + + epk_ = com.google.protobuf.ByteString.EMPTY; + + ciphertext_ = com.google.protobuf.ByteString.EMPTY; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.CompactFormats.internal_static_cash_z_wallet_sdk_rpc_CompactOutput_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput build() { + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput buildPartial() { + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput result = new cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput(this); + result.cmu_ = cmu_; + result.epk_ = epk_; + result.ciphertext_ = ciphertext_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput) { + return mergeFrom((cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput other) { + if (other == cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput.getDefaultInstance()) return this; + if (other.getCmu() != com.google.protobuf.ByteString.EMPTY) { + setCmu(other.getCmu()); + } + if (other.getEpk() != com.google.protobuf.ByteString.EMPTY) { + setEpk(other.getEpk()); + } + if (other.getCiphertext() != com.google.protobuf.ByteString.EMPTY) { + setCiphertext(other.getCiphertext()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private com.google.protobuf.ByteString cmu_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * note commitment u-coordinate
+       * 
+ * + * bytes cmu = 1; + * @return The cmu. + */ + @java.lang.Override + public com.google.protobuf.ByteString getCmu() { + return cmu_; + } + /** + *
+       * note commitment u-coordinate
+       * 
+ * + * bytes cmu = 1; + * @param value The cmu to set. + * @return This builder for chaining. + */ + public Builder setCmu(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + cmu_ = value; + onChanged(); + return this; + } + /** + *
+       * note commitment u-coordinate
+       * 
+ * + * bytes cmu = 1; + * @return This builder for chaining. + */ + public Builder clearCmu() { + + cmu_ = getDefaultInstance().getCmu(); + onChanged(); + return this; + } + + private com.google.protobuf.ByteString epk_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * ephemeral public key
+       * 
+ * + * bytes epk = 2; + * @return The epk. + */ + @java.lang.Override + public com.google.protobuf.ByteString getEpk() { + return epk_; + } + /** + *
+       * ephemeral public key
+       * 
+ * + * bytes epk = 2; + * @param value The epk to set. + * @return This builder for chaining. + */ + public Builder setEpk(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + epk_ = value; + onChanged(); + return this; + } + /** + *
+       * ephemeral public key
+       * 
+ * + * bytes epk = 2; + * @return This builder for chaining. + */ + public Builder clearEpk() { + + epk_ = getDefaultInstance().getEpk(); + onChanged(); + return this; + } + + private com.google.protobuf.ByteString ciphertext_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * ciphertext and zkproof
+       * 
+ * + * bytes ciphertext = 3; + * @return The ciphertext. + */ + @java.lang.Override + public com.google.protobuf.ByteString getCiphertext() { + return ciphertext_; + } + /** + *
+       * ciphertext and zkproof
+       * 
+ * + * bytes ciphertext = 3; + * @param value The ciphertext to set. + * @return This builder for chaining. + */ + public Builder setCiphertext(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + ciphertext_ = value; + onChanged(); + return this; + } + /** + *
+       * ciphertext and zkproof
+       * 
+ * + * bytes ciphertext = 3; + * @return This builder for chaining. + */ + public Builder clearCiphertext() { + + ciphertext_ = getDefaultInstance().getCiphertext(); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.CompactOutput) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.CompactOutput) + private static final cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput(); + } + + public static cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public CompactOutput parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new CompactOutput(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.CompactFormats.CompactOutput getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_CompactBlock_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_CompactBlock_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_CompactTx_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_CompactTx_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_CompactSpend_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_CompactSpend_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_CompactOutput_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_CompactOutput_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\025compact_formats.proto\022\025cash.z.wallet.s" + + "dk.rpc\"\241\001\n\014CompactBlock\022\024\n\014protoVersion\030" + + "\001 \001(\r\022\016\n\006height\030\002 \001(\004\022\014\n\004hash\030\003 \001(\014\022\020\n\010p" + + "revHash\030\004 \001(\014\022\014\n\004time\030\005 \001(\r\022\016\n\006header\030\006 " + + "\001(\014\022-\n\003vtx\030\007 \003(\0132 .cash.z.wallet.sdk.rpc" + + ".CompactTx\"\241\001\n\tCompactTx\022\r\n\005index\030\001 \001(\004\022" + + "\014\n\004hash\030\002 \001(\014\022\013\n\003fee\030\003 \001(\r\0223\n\006spends\030\004 \003" + + "(\0132#.cash.z.wallet.sdk.rpc.CompactSpend\022" + + "5\n\007outputs\030\005 \003(\0132$.cash.z.wallet.sdk.rpc" + + ".CompactOutput\"\032\n\014CompactSpend\022\n\n\002nf\030\001 \001" + + "(\014\"=\n\rCompactOutput\022\013\n\003cmu\030\001 \001(\014\022\013\n\003epk\030" + + "\002 \001(\014\022\022\n\nciphertext\030\003 \001(\014B\033Z\026lightwallet" + + "d/walletrpc\272\002\000b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }); + internal_static_cash_z_wallet_sdk_rpc_CompactBlock_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_cash_z_wallet_sdk_rpc_CompactBlock_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_CompactBlock_descriptor, + new java.lang.String[] { "ProtoVersion", "Height", "Hash", "PrevHash", "Time", "Header", "Vtx", }); + internal_static_cash_z_wallet_sdk_rpc_CompactTx_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_cash_z_wallet_sdk_rpc_CompactTx_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_CompactTx_descriptor, + new java.lang.String[] { "Index", "Hash", "Fee", "Spends", "Outputs", }); + internal_static_cash_z_wallet_sdk_rpc_CompactSpend_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_cash_z_wallet_sdk_rpc_CompactSpend_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_CompactSpend_descriptor, + new java.lang.String[] { "Nf", }); + internal_static_cash_z_wallet_sdk_rpc_CompactOutput_descriptor = + getDescriptor().getMessageTypes().get(3); + internal_static_cash_z_wallet_sdk_rpc_CompactOutput_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_CompactOutput_descriptor, + new java.lang.String[] { "Cmu", "Epk", "Ciphertext", }); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/src/main/java/cash/z/wallet/sdk/rpc/CompactTxStreamerGrpc.java b/src/main/java/cash/z/wallet/sdk/rpc/CompactTxStreamerGrpc.java new file mode 100644 index 00000000..53b64c05 --- /dev/null +++ b/src/main/java/cash/z/wallet/sdk/rpc/CompactTxStreamerGrpc.java @@ -0,0 +1,1341 @@ +package cash.z.wallet.sdk.rpc; + +import static io.grpc.MethodDescriptor.generateFullMethodName; + +/** + */ +@javax.annotation.Generated( + value = "by gRPC proto compiler (version 1.45.1)", + comments = "Source: service.proto") +@io.grpc.stub.annotations.GrpcGenerated +public final class CompactTxStreamerGrpc { + + private CompactTxStreamerGrpc() {} + + public static final String SERVICE_NAME = "cash.z.wallet.sdk.rpc.CompactTxStreamer"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getGetLatestBlockMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetLatestBlock", + requestType = cash.z.wallet.sdk.rpc.Service.ChainSpec.class, + responseType = cash.z.wallet.sdk.rpc.Service.BlockID.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetLatestBlockMethod() { + io.grpc.MethodDescriptor getGetLatestBlockMethod; + if ((getGetLatestBlockMethod = CompactTxStreamerGrpc.getGetLatestBlockMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetLatestBlockMethod = CompactTxStreamerGrpc.getGetLatestBlockMethod) == null) { + CompactTxStreamerGrpc.getGetLatestBlockMethod = getGetLatestBlockMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetLatestBlock")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.ChainSpec.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetLatestBlock")) + .build(); + } + } + } + return getGetLatestBlockMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetBlockMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetBlock", + requestType = cash.z.wallet.sdk.rpc.Service.BlockID.class, + responseType = cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetBlockMethod() { + io.grpc.MethodDescriptor getGetBlockMethod; + if ((getGetBlockMethod = CompactTxStreamerGrpc.getGetBlockMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetBlockMethod = CompactTxStreamerGrpc.getGetBlockMethod) == null) { + CompactTxStreamerGrpc.getGetBlockMethod = getGetBlockMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetBlock")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetBlock")) + .build(); + } + } + } + return getGetBlockMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetBlockRangeMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetBlockRange", + requestType = cash.z.wallet.sdk.rpc.Service.BlockRange.class, + responseType = cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.class, + methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + public static io.grpc.MethodDescriptor getGetBlockRangeMethod() { + io.grpc.MethodDescriptor getGetBlockRangeMethod; + if ((getGetBlockRangeMethod = CompactTxStreamerGrpc.getGetBlockRangeMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetBlockRangeMethod = CompactTxStreamerGrpc.getGetBlockRangeMethod) == null) { + CompactTxStreamerGrpc.getGetBlockRangeMethod = getGetBlockRangeMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetBlockRange")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.BlockRange.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetBlockRange")) + .build(); + } + } + } + return getGetBlockRangeMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetTransactionMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetTransaction", + requestType = cash.z.wallet.sdk.rpc.Service.TxFilter.class, + responseType = cash.z.wallet.sdk.rpc.Service.RawTransaction.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetTransactionMethod() { + io.grpc.MethodDescriptor getGetTransactionMethod; + if ((getGetTransactionMethod = CompactTxStreamerGrpc.getGetTransactionMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetTransactionMethod = CompactTxStreamerGrpc.getGetTransactionMethod) == null) { + CompactTxStreamerGrpc.getGetTransactionMethod = getGetTransactionMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetTransaction")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.TxFilter.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.RawTransaction.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetTransaction")) + .build(); + } + } + } + return getGetTransactionMethod; + } + + private static volatile io.grpc.MethodDescriptor getSendTransactionMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "SendTransaction", + requestType = cash.z.wallet.sdk.rpc.Service.RawTransaction.class, + responseType = cash.z.wallet.sdk.rpc.Service.SendResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getSendTransactionMethod() { + io.grpc.MethodDescriptor getSendTransactionMethod; + if ((getSendTransactionMethod = CompactTxStreamerGrpc.getSendTransactionMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getSendTransactionMethod = CompactTxStreamerGrpc.getSendTransactionMethod) == null) { + CompactTxStreamerGrpc.getSendTransactionMethod = getSendTransactionMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SendTransaction")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.RawTransaction.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.SendResponse.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("SendTransaction")) + .build(); + } + } + } + return getSendTransactionMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetTaddressTxidsMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetTaddressTxids", + requestType = cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.class, + responseType = cash.z.wallet.sdk.rpc.Service.RawTransaction.class, + methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + public static io.grpc.MethodDescriptor getGetTaddressTxidsMethod() { + io.grpc.MethodDescriptor getGetTaddressTxidsMethod; + if ((getGetTaddressTxidsMethod = CompactTxStreamerGrpc.getGetTaddressTxidsMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetTaddressTxidsMethod = CompactTxStreamerGrpc.getGetTaddressTxidsMethod) == null) { + CompactTxStreamerGrpc.getGetTaddressTxidsMethod = getGetTaddressTxidsMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetTaddressTxids")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.RawTransaction.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetTaddressTxids")) + .build(); + } + } + } + return getGetTaddressTxidsMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetTaddressBalanceMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetTaddressBalance", + requestType = cash.z.wallet.sdk.rpc.Service.AddressList.class, + responseType = cash.z.wallet.sdk.rpc.Service.Balance.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetTaddressBalanceMethod() { + io.grpc.MethodDescriptor getGetTaddressBalanceMethod; + if ((getGetTaddressBalanceMethod = CompactTxStreamerGrpc.getGetTaddressBalanceMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetTaddressBalanceMethod = CompactTxStreamerGrpc.getGetTaddressBalanceMethod) == null) { + CompactTxStreamerGrpc.getGetTaddressBalanceMethod = getGetTaddressBalanceMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetTaddressBalance")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.AddressList.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Balance.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetTaddressBalance")) + .build(); + } + } + } + return getGetTaddressBalanceMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetTaddressBalanceStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetTaddressBalanceStream", + requestType = cash.z.wallet.sdk.rpc.Service.Address.class, + responseType = cash.z.wallet.sdk.rpc.Service.Balance.class, + methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + public static io.grpc.MethodDescriptor getGetTaddressBalanceStreamMethod() { + io.grpc.MethodDescriptor getGetTaddressBalanceStreamMethod; + if ((getGetTaddressBalanceStreamMethod = CompactTxStreamerGrpc.getGetTaddressBalanceStreamMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetTaddressBalanceStreamMethod = CompactTxStreamerGrpc.getGetTaddressBalanceStreamMethod) == null) { + CompactTxStreamerGrpc.getGetTaddressBalanceStreamMethod = getGetTaddressBalanceStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetTaddressBalanceStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Address.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Balance.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetTaddressBalanceStream")) + .build(); + } + } + } + return getGetTaddressBalanceStreamMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetMempoolTxMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetMempoolTx", + requestType = cash.z.wallet.sdk.rpc.Service.Exclude.class, + responseType = cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.class, + methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + public static io.grpc.MethodDescriptor getGetMempoolTxMethod() { + io.grpc.MethodDescriptor getGetMempoolTxMethod; + if ((getGetMempoolTxMethod = CompactTxStreamerGrpc.getGetMempoolTxMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetMempoolTxMethod = CompactTxStreamerGrpc.getGetMempoolTxMethod) == null) { + CompactTxStreamerGrpc.getGetMempoolTxMethod = getGetMempoolTxMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetMempoolTx")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Exclude.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetMempoolTx")) + .build(); + } + } + } + return getGetMempoolTxMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetTreeStateMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetTreeState", + requestType = cash.z.wallet.sdk.rpc.Service.BlockID.class, + responseType = cash.z.wallet.sdk.rpc.Service.TreeState.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetTreeStateMethod() { + io.grpc.MethodDescriptor getGetTreeStateMethod; + if ((getGetTreeStateMethod = CompactTxStreamerGrpc.getGetTreeStateMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetTreeStateMethod = CompactTxStreamerGrpc.getGetTreeStateMethod) == null) { + CompactTxStreamerGrpc.getGetTreeStateMethod = getGetTreeStateMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetTreeState")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.TreeState.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetTreeState")) + .build(); + } + } + } + return getGetTreeStateMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetAddressUtxosMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetAddressUtxos", + requestType = cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.class, + responseType = cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetAddressUtxosMethod() { + io.grpc.MethodDescriptor getGetAddressUtxosMethod; + if ((getGetAddressUtxosMethod = CompactTxStreamerGrpc.getGetAddressUtxosMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetAddressUtxosMethod = CompactTxStreamerGrpc.getGetAddressUtxosMethod) == null) { + CompactTxStreamerGrpc.getGetAddressUtxosMethod = getGetAddressUtxosMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetAddressUtxos")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetAddressUtxos")) + .build(); + } + } + } + return getGetAddressUtxosMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetAddressUtxosStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetAddressUtxosStream", + requestType = cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.class, + responseType = cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.class, + methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + public static io.grpc.MethodDescriptor getGetAddressUtxosStreamMethod() { + io.grpc.MethodDescriptor getGetAddressUtxosStreamMethod; + if ((getGetAddressUtxosStreamMethod = CompactTxStreamerGrpc.getGetAddressUtxosStreamMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetAddressUtxosStreamMethod = CompactTxStreamerGrpc.getGetAddressUtxosStreamMethod) == null) { + CompactTxStreamerGrpc.getGetAddressUtxosStreamMethod = getGetAddressUtxosStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetAddressUtxosStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetAddressUtxosStream")) + .build(); + } + } + } + return getGetAddressUtxosStreamMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetLightdInfoMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetLightdInfo", + requestType = cash.z.wallet.sdk.rpc.Service.Empty.class, + responseType = cash.z.wallet.sdk.rpc.Service.LightdInfo.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getGetLightdInfoMethod() { + io.grpc.MethodDescriptor getGetLightdInfoMethod; + if ((getGetLightdInfoMethod = CompactTxStreamerGrpc.getGetLightdInfoMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getGetLightdInfoMethod = CompactTxStreamerGrpc.getGetLightdInfoMethod) == null) { + CompactTxStreamerGrpc.getGetLightdInfoMethod = getGetLightdInfoMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetLightdInfo")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.LightdInfo.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("GetLightdInfo")) + .build(); + } + } + } + return getGetLightdInfoMethod; + } + + private static volatile io.grpc.MethodDescriptor getPingMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "Ping", + requestType = cash.z.wallet.sdk.rpc.Service.Duration.class, + responseType = cash.z.wallet.sdk.rpc.Service.PingResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getPingMethod() { + io.grpc.MethodDescriptor getPingMethod; + if ((getPingMethod = CompactTxStreamerGrpc.getPingMethod) == null) { + synchronized (CompactTxStreamerGrpc.class) { + if ((getPingMethod = CompactTxStreamerGrpc.getPingMethod) == null) { + CompactTxStreamerGrpc.getPingMethod = getPingMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "Ping")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Duration.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.PingResponse.getDefaultInstance())) + .setSchemaDescriptor(new CompactTxStreamerMethodDescriptorSupplier("Ping")) + .build(); + } + } + } + return getPingMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static CompactTxStreamerStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public CompactTxStreamerStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new CompactTxStreamerStub(channel, callOptions); + } + }; + return CompactTxStreamerStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static CompactTxStreamerBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public CompactTxStreamerBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new CompactTxStreamerBlockingStub(channel, callOptions); + } + }; + return CompactTxStreamerBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static CompactTxStreamerFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public CompactTxStreamerFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new CompactTxStreamerFutureStub(channel, callOptions); + } + }; + return CompactTxStreamerFutureStub.newStub(factory, channel); + } + + /** + */ + public static abstract class CompactTxStreamerImplBase implements io.grpc.BindableService { + + /** + *
+     * Return the height of the tip of the best chain
+     * 
+ */ + public void getLatestBlock(cash.z.wallet.sdk.rpc.Service.ChainSpec request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetLatestBlockMethod(), responseObserver); + } + + /** + *
+     * Return the compact block corresponding to the given block identifier
+     * 
+ */ + public void getBlock(cash.z.wallet.sdk.rpc.Service.BlockID request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetBlockMethod(), responseObserver); + } + + /** + *
+     * Return a list of consecutive compact blocks
+     * 
+ */ + public void getBlockRange(cash.z.wallet.sdk.rpc.Service.BlockRange request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetBlockRangeMethod(), responseObserver); + } + + /** + *
+     * Return the requested full (not compact) transaction (as from pirated)
+     * 
+ */ + public void getTransaction(cash.z.wallet.sdk.rpc.Service.TxFilter request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetTransactionMethod(), responseObserver); + } + + /** + *
+     * Submit the given transaction to the Zcash network
+     * 
+ */ + public void sendTransaction(cash.z.wallet.sdk.rpc.Service.RawTransaction request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getSendTransactionMethod(), responseObserver); + } + + /** + *
+     * Return the txids corresponding to the given t-address within the given block range
+     * 
+ */ + public void getTaddressTxids(cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetTaddressTxidsMethod(), responseObserver); + } + + /** + */ + public void getTaddressBalance(cash.z.wallet.sdk.rpc.Service.AddressList request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetTaddressBalanceMethod(), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver getTaddressBalanceStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getGetTaddressBalanceStreamMethod(), responseObserver); + } + + /** + *
+     * Return the compact transactions currently in the mempool; the results
+     * can be a few seconds out of date. If the Exclude list is empty, return
+     * all transactions; otherwise return all *except* those in the Exclude list
+     * (if any); this allows the client to avoid receiving transactions that it
+     * already has (from an earlier call to this rpc). The transaction IDs in the
+     * Exclude list can be shortened to any number of bytes to make the request
+     * more bandwidth-efficient; if two or more transactions in the mempool
+     * match a shortened txid, they are all sent (none is excluded). Transactions
+     * in the exclude list that don't exist in the mempool are ignored.
+     * 
+ */ + public void getMempoolTx(cash.z.wallet.sdk.rpc.Service.Exclude request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetMempoolTxMethod(), responseObserver); + } + + /** + *
+     * GetTreeState returns the note commitment tree state corresponding to the given block.
+     * See section 3.7 of the Zcash protocol specification. It returns several other useful
+     * values also (even though they can be obtained using GetBlock).
+     * The block can be specified by either height or hash.
+     * 
+ */ + public void getTreeState(cash.z.wallet.sdk.rpc.Service.BlockID request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetTreeStateMethod(), responseObserver); + } + + /** + */ + public void getAddressUtxos(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetAddressUtxosMethod(), responseObserver); + } + + /** + */ + public void getAddressUtxosStream(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetAddressUtxosStreamMethod(), responseObserver); + } + + /** + *
+     * Return information about this lightwalletd instance and the blockchain
+     * 
+ */ + public void getLightdInfo(cash.z.wallet.sdk.rpc.Service.Empty request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetLightdInfoMethod(), responseObserver); + } + + /** + *
+     * Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production)
+     * 
+ */ + public void ping(cash.z.wallet.sdk.rpc.Service.Duration request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getPingMethod(), responseObserver); + } + + @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getGetLatestBlockMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.ChainSpec, + cash.z.wallet.sdk.rpc.Service.BlockID>( + this, METHODID_GET_LATEST_BLOCK))) + .addMethod( + getGetBlockMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.BlockID, + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock>( + this, METHODID_GET_BLOCK))) + .addMethod( + getGetBlockRangeMethod(), + io.grpc.stub.ServerCalls.asyncServerStreamingCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.BlockRange, + cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock>( + this, METHODID_GET_BLOCK_RANGE))) + .addMethod( + getGetTransactionMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.TxFilter, + cash.z.wallet.sdk.rpc.Service.RawTransaction>( + this, METHODID_GET_TRANSACTION))) + .addMethod( + getSendTransactionMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.RawTransaction, + cash.z.wallet.sdk.rpc.Service.SendResponse>( + this, METHODID_SEND_TRANSACTION))) + .addMethod( + getGetTaddressTxidsMethod(), + io.grpc.stub.ServerCalls.asyncServerStreamingCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter, + cash.z.wallet.sdk.rpc.Service.RawTransaction>( + this, METHODID_GET_TADDRESS_TXIDS))) + .addMethod( + getGetTaddressBalanceMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.AddressList, + cash.z.wallet.sdk.rpc.Service.Balance>( + this, METHODID_GET_TADDRESS_BALANCE))) + .addMethod( + getGetTaddressBalanceStreamMethod(), + io.grpc.stub.ServerCalls.asyncClientStreamingCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.Address, + cash.z.wallet.sdk.rpc.Service.Balance>( + this, METHODID_GET_TADDRESS_BALANCE_STREAM))) + .addMethod( + getGetMempoolTxMethod(), + io.grpc.stub.ServerCalls.asyncServerStreamingCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.Exclude, + cash.z.wallet.sdk.rpc.CompactFormats.CompactTx>( + this, METHODID_GET_MEMPOOL_TX))) + .addMethod( + getGetTreeStateMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.BlockID, + cash.z.wallet.sdk.rpc.Service.TreeState>( + this, METHODID_GET_TREE_STATE))) + .addMethod( + getGetAddressUtxosMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg, + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList>( + this, METHODID_GET_ADDRESS_UTXOS))) + .addMethod( + getGetAddressUtxosStreamMethod(), + io.grpc.stub.ServerCalls.asyncServerStreamingCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg, + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply>( + this, METHODID_GET_ADDRESS_UTXOS_STREAM))) + .addMethod( + getGetLightdInfoMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.Empty, + cash.z.wallet.sdk.rpc.Service.LightdInfo>( + this, METHODID_GET_LIGHTD_INFO))) + .addMethod( + getPingMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.Duration, + cash.z.wallet.sdk.rpc.Service.PingResponse>( + this, METHODID_PING))) + .build(); + } + } + + /** + */ + public static final class CompactTxStreamerStub extends io.grpc.stub.AbstractAsyncStub { + private CompactTxStreamerStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected CompactTxStreamerStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new CompactTxStreamerStub(channel, callOptions); + } + + /** + *
+     * Return the height of the tip of the best chain
+     * 
+ */ + public void getLatestBlock(cash.z.wallet.sdk.rpc.Service.ChainSpec request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetLatestBlockMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Return the compact block corresponding to the given block identifier
+     * 
+ */ + public void getBlock(cash.z.wallet.sdk.rpc.Service.BlockID request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetBlockMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Return a list of consecutive compact blocks
+     * 
+ */ + public void getBlockRange(cash.z.wallet.sdk.rpc.Service.BlockRange request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncServerStreamingCall( + getChannel().newCall(getGetBlockRangeMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Return the requested full (not compact) transaction (as from pirated)
+     * 
+ */ + public void getTransaction(cash.z.wallet.sdk.rpc.Service.TxFilter request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetTransactionMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Submit the given transaction to the Zcash network
+     * 
+ */ + public void sendTransaction(cash.z.wallet.sdk.rpc.Service.RawTransaction request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getSendTransactionMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Return the txids corresponding to the given t-address within the given block range
+     * 
+ */ + public void getTaddressTxids(cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncServerStreamingCall( + getChannel().newCall(getGetTaddressTxidsMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public void getTaddressBalance(cash.z.wallet.sdk.rpc.Service.AddressList request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetTaddressBalanceMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver getTaddressBalanceStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncClientStreamingCall( + getChannel().newCall(getGetTaddressBalanceStreamMethod(), getCallOptions()), responseObserver); + } + + /** + *
+     * Return the compact transactions currently in the mempool; the results
+     * can be a few seconds out of date. If the Exclude list is empty, return
+     * all transactions; otherwise return all *except* those in the Exclude list
+     * (if any); this allows the client to avoid receiving transactions that it
+     * already has (from an earlier call to this rpc). The transaction IDs in the
+     * Exclude list can be shortened to any number of bytes to make the request
+     * more bandwidth-efficient; if two or more transactions in the mempool
+     * match a shortened txid, they are all sent (none is excluded). Transactions
+     * in the exclude list that don't exist in the mempool are ignored.
+     * 
+ */ + public void getMempoolTx(cash.z.wallet.sdk.rpc.Service.Exclude request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncServerStreamingCall( + getChannel().newCall(getGetMempoolTxMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * GetTreeState returns the note commitment tree state corresponding to the given block.
+     * See section 3.7 of the Zcash protocol specification. It returns several other useful
+     * values also (even though they can be obtained using GetBlock).
+     * The block can be specified by either height or hash.
+     * 
+ */ + public void getTreeState(cash.z.wallet.sdk.rpc.Service.BlockID request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetTreeStateMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public void getAddressUtxos(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetAddressUtxosMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public void getAddressUtxosStream(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncServerStreamingCall( + getChannel().newCall(getGetAddressUtxosStreamMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Return information about this lightwalletd instance and the blockchain
+     * 
+ */ + public void getLightdInfo(cash.z.wallet.sdk.rpc.Service.Empty request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getGetLightdInfoMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production)
+     * 
+ */ + public void ping(cash.z.wallet.sdk.rpc.Service.Duration request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getPingMethod(), getCallOptions()), request, responseObserver); + } + } + + /** + */ + public static final class CompactTxStreamerBlockingStub extends io.grpc.stub.AbstractBlockingStub { + private CompactTxStreamerBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected CompactTxStreamerBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new CompactTxStreamerBlockingStub(channel, callOptions); + } + + /** + *
+     * Return the height of the tip of the best chain
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.BlockID getLatestBlock(cash.z.wallet.sdk.rpc.Service.ChainSpec request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetLatestBlockMethod(), getCallOptions(), request); + } + + /** + *
+     * Return the compact block corresponding to the given block identifier
+     * 
+ */ + public cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock getBlock(cash.z.wallet.sdk.rpc.Service.BlockID request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetBlockMethod(), getCallOptions(), request); + } + + /** + *
+     * Return a list of consecutive compact blocks
+     * 
+ */ + public java.util.Iterator getBlockRange( + cash.z.wallet.sdk.rpc.Service.BlockRange request) { + return io.grpc.stub.ClientCalls.blockingServerStreamingCall( + getChannel(), getGetBlockRangeMethod(), getCallOptions(), request); + } + + /** + *
+     * Return the requested full (not compact) transaction (as from pirated)
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.RawTransaction getTransaction(cash.z.wallet.sdk.rpc.Service.TxFilter request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetTransactionMethod(), getCallOptions(), request); + } + + /** + *
+     * Submit the given transaction to the Zcash network
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.SendResponse sendTransaction(cash.z.wallet.sdk.rpc.Service.RawTransaction request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getSendTransactionMethod(), getCallOptions(), request); + } + + /** + *
+     * Return the txids corresponding to the given t-address within the given block range
+     * 
+ */ + public java.util.Iterator getTaddressTxids( + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter request) { + return io.grpc.stub.ClientCalls.blockingServerStreamingCall( + getChannel(), getGetTaddressTxidsMethod(), getCallOptions(), request); + } + + /** + */ + public cash.z.wallet.sdk.rpc.Service.Balance getTaddressBalance(cash.z.wallet.sdk.rpc.Service.AddressList request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetTaddressBalanceMethod(), getCallOptions(), request); + } + + /** + *
+     * Return the compact transactions currently in the mempool; the results
+     * can be a few seconds out of date. If the Exclude list is empty, return
+     * all transactions; otherwise return all *except* those in the Exclude list
+     * (if any); this allows the client to avoid receiving transactions that it
+     * already has (from an earlier call to this rpc). The transaction IDs in the
+     * Exclude list can be shortened to any number of bytes to make the request
+     * more bandwidth-efficient; if two or more transactions in the mempool
+     * match a shortened txid, they are all sent (none is excluded). Transactions
+     * in the exclude list that don't exist in the mempool are ignored.
+     * 
+ */ + public java.util.Iterator getMempoolTx( + cash.z.wallet.sdk.rpc.Service.Exclude request) { + return io.grpc.stub.ClientCalls.blockingServerStreamingCall( + getChannel(), getGetMempoolTxMethod(), getCallOptions(), request); + } + + /** + *
+     * GetTreeState returns the note commitment tree state corresponding to the given block.
+     * See section 3.7 of the Zcash protocol specification. It returns several other useful
+     * values also (even though they can be obtained using GetBlock).
+     * The block can be specified by either height or hash.
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.TreeState getTreeState(cash.z.wallet.sdk.rpc.Service.BlockID request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetTreeStateMethod(), getCallOptions(), request); + } + + /** + */ + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList getAddressUtxos(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetAddressUtxosMethod(), getCallOptions(), request); + } + + /** + */ + public java.util.Iterator getAddressUtxosStream( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg request) { + return io.grpc.stub.ClientCalls.blockingServerStreamingCall( + getChannel(), getGetAddressUtxosStreamMethod(), getCallOptions(), request); + } + + /** + *
+     * Return information about this lightwalletd instance and the blockchain
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.LightdInfo getLightdInfo(cash.z.wallet.sdk.rpc.Service.Empty request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getGetLightdInfoMethod(), getCallOptions(), request); + } + + /** + *
+     * Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production)
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.PingResponse ping(cash.z.wallet.sdk.rpc.Service.Duration request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getPingMethod(), getCallOptions(), request); + } + } + + /** + */ + public static final class CompactTxStreamerFutureStub extends io.grpc.stub.AbstractFutureStub { + private CompactTxStreamerFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected CompactTxStreamerFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new CompactTxStreamerFutureStub(channel, callOptions); + } + + /** + *
+     * Return the height of the tip of the best chain
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture getLatestBlock( + cash.z.wallet.sdk.rpc.Service.ChainSpec request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetLatestBlockMethod(), getCallOptions()), request); + } + + /** + *
+     * Return the compact block corresponding to the given block identifier
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture getBlock( + cash.z.wallet.sdk.rpc.Service.BlockID request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetBlockMethod(), getCallOptions()), request); + } + + /** + *
+     * Return the requested full (not compact) transaction (as from pirated)
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture getTransaction( + cash.z.wallet.sdk.rpc.Service.TxFilter request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetTransactionMethod(), getCallOptions()), request); + } + + /** + *
+     * Submit the given transaction to the Zcash network
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture sendTransaction( + cash.z.wallet.sdk.rpc.Service.RawTransaction request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getSendTransactionMethod(), getCallOptions()), request); + } + + /** + */ + public com.google.common.util.concurrent.ListenableFuture getTaddressBalance( + cash.z.wallet.sdk.rpc.Service.AddressList request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetTaddressBalanceMethod(), getCallOptions()), request); + } + + /** + *
+     * GetTreeState returns the note commitment tree state corresponding to the given block.
+     * See section 3.7 of the Zcash protocol specification. It returns several other useful
+     * values also (even though they can be obtained using GetBlock).
+     * The block can be specified by either height or hash.
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture getTreeState( + cash.z.wallet.sdk.rpc.Service.BlockID request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetTreeStateMethod(), getCallOptions()), request); + } + + /** + */ + public com.google.common.util.concurrent.ListenableFuture getAddressUtxos( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetAddressUtxosMethod(), getCallOptions()), request); + } + + /** + *
+     * Return information about this lightwalletd instance and the blockchain
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture getLightdInfo( + cash.z.wallet.sdk.rpc.Service.Empty request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getGetLightdInfoMethod(), getCallOptions()), request); + } + + /** + *
+     * Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production)
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture ping( + cash.z.wallet.sdk.rpc.Service.Duration request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getPingMethod(), getCallOptions()), request); + } + } + + private static final int METHODID_GET_LATEST_BLOCK = 0; + private static final int METHODID_GET_BLOCK = 1; + private static final int METHODID_GET_BLOCK_RANGE = 2; + private static final int METHODID_GET_TRANSACTION = 3; + private static final int METHODID_SEND_TRANSACTION = 4; + private static final int METHODID_GET_TADDRESS_TXIDS = 5; + private static final int METHODID_GET_TADDRESS_BALANCE = 6; + private static final int METHODID_GET_MEMPOOL_TX = 7; + private static final int METHODID_GET_TREE_STATE = 8; + private static final int METHODID_GET_ADDRESS_UTXOS = 9; + private static final int METHODID_GET_ADDRESS_UTXOS_STREAM = 10; + private static final int METHODID_GET_LIGHTD_INFO = 11; + private static final int METHODID_PING = 12; + private static final int METHODID_GET_TADDRESS_BALANCE_STREAM = 13; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final CompactTxStreamerImplBase serviceImpl; + private final int methodId; + + MethodHandlers(CompactTxStreamerImplBase serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_GET_LATEST_BLOCK: + serviceImpl.getLatestBlock((cash.z.wallet.sdk.rpc.Service.ChainSpec) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_BLOCK: + serviceImpl.getBlock((cash.z.wallet.sdk.rpc.Service.BlockID) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_BLOCK_RANGE: + serviceImpl.getBlockRange((cash.z.wallet.sdk.rpc.Service.BlockRange) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_TRANSACTION: + serviceImpl.getTransaction((cash.z.wallet.sdk.rpc.Service.TxFilter) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_SEND_TRANSACTION: + serviceImpl.sendTransaction((cash.z.wallet.sdk.rpc.Service.RawTransaction) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_TADDRESS_TXIDS: + serviceImpl.getTaddressTxids((cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_TADDRESS_BALANCE: + serviceImpl.getTaddressBalance((cash.z.wallet.sdk.rpc.Service.AddressList) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_MEMPOOL_TX: + serviceImpl.getMempoolTx((cash.z.wallet.sdk.rpc.Service.Exclude) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_TREE_STATE: + serviceImpl.getTreeState((cash.z.wallet.sdk.rpc.Service.BlockID) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_ADDRESS_UTXOS: + serviceImpl.getAddressUtxos((cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_ADDRESS_UTXOS_STREAM: + serviceImpl.getAddressUtxosStream((cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_LIGHTD_INFO: + serviceImpl.getLightdInfo((cash.z.wallet.sdk.rpc.Service.Empty) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_PING: + serviceImpl.ping((cash.z.wallet.sdk.rpc.Service.Duration) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + default: + throw new AssertionError(); + } + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_GET_TADDRESS_BALANCE_STREAM: + return (io.grpc.stub.StreamObserver) serviceImpl.getTaddressBalanceStream( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + private static abstract class CompactTxStreamerBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + CompactTxStreamerBaseDescriptorSupplier() {} + + @java.lang.Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return cash.z.wallet.sdk.rpc.Service.getDescriptor(); + } + + @java.lang.Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("CompactTxStreamer"); + } + } + + private static final class CompactTxStreamerFileDescriptorSupplier + extends CompactTxStreamerBaseDescriptorSupplier { + CompactTxStreamerFileDescriptorSupplier() {} + } + + private static final class CompactTxStreamerMethodDescriptorSupplier + extends CompactTxStreamerBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final String methodName; + + CompactTxStreamerMethodDescriptorSupplier(String methodName) { + this.methodName = methodName; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (CompactTxStreamerGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new CompactTxStreamerFileDescriptorSupplier()) + .addMethod(getGetLatestBlockMethod()) + .addMethod(getGetBlockMethod()) + .addMethod(getGetBlockRangeMethod()) + .addMethod(getGetTransactionMethod()) + .addMethod(getSendTransactionMethod()) + .addMethod(getGetTaddressTxidsMethod()) + .addMethod(getGetTaddressBalanceMethod()) + .addMethod(getGetTaddressBalanceStreamMethod()) + .addMethod(getGetMempoolTxMethod()) + .addMethod(getGetTreeStateMethod()) + .addMethod(getGetAddressUtxosMethod()) + .addMethod(getGetAddressUtxosStreamMethod()) + .addMethod(getGetLightdInfoMethod()) + .addMethod(getPingMethod()) + .build(); + } + } + } + return result; + } +} diff --git a/src/main/java/cash/z/wallet/sdk/rpc/Darkside.java b/src/main/java/cash/z/wallet/sdk/rpc/Darkside.java new file mode 100644 index 00000000..d35f4ae7 --- /dev/null +++ b/src/main/java/cash/z/wallet/sdk/rpc/Darkside.java @@ -0,0 +1,3854 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: darkside.proto + +package cash.z.wallet.sdk.rpc; + +public final class Darkside { + private Darkside() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + public interface DarksideMetaStateOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.DarksideMetaState) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 saplingActivation = 1; + * @return The saplingActivation. + */ + int getSaplingActivation(); + + /** + * string branchID = 2; + * @return The branchID. + */ + java.lang.String getBranchID(); + /** + * string branchID = 2; + * @return The bytes for branchID. + */ + com.google.protobuf.ByteString + getBranchIDBytes(); + + /** + * string chainName = 3; + * @return The chainName. + */ + java.lang.String getChainName(); + /** + * string chainName = 3; + * @return The bytes for chainName. + */ + com.google.protobuf.ByteString + getChainNameBytes(); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideMetaState} + */ + public static final class DarksideMetaState extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.DarksideMetaState) + DarksideMetaStateOrBuilder { + private static final long serialVersionUID = 0L; + // Use DarksideMetaState.newBuilder() to construct. + private DarksideMetaState(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private DarksideMetaState() { + branchID_ = ""; + chainName_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new DarksideMetaState(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private DarksideMetaState( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + saplingActivation_ = input.readInt32(); + break; + } + case 18: { + java.lang.String s = input.readStringRequireUtf8(); + + branchID_ = s; + break; + } + case 26: { + java.lang.String s = input.readStringRequireUtf8(); + + chainName_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.class, cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.Builder.class); + } + + public static final int SAPLINGACTIVATION_FIELD_NUMBER = 1; + private int saplingActivation_; + /** + * int32 saplingActivation = 1; + * @return The saplingActivation. + */ + @java.lang.Override + public int getSaplingActivation() { + return saplingActivation_; + } + + public static final int BRANCHID_FIELD_NUMBER = 2; + private volatile java.lang.Object branchID_; + /** + * string branchID = 2; + * @return The branchID. + */ + @java.lang.Override + public java.lang.String getBranchID() { + java.lang.Object ref = branchID_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + branchID_ = s; + return s; + } + } + /** + * string branchID = 2; + * @return The bytes for branchID. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getBranchIDBytes() { + java.lang.Object ref = branchID_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + branchID_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int CHAINNAME_FIELD_NUMBER = 3; + private volatile java.lang.Object chainName_; + /** + * string chainName = 3; + * @return The chainName. + */ + @java.lang.Override + public java.lang.String getChainName() { + java.lang.Object ref = chainName_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + chainName_ = s; + return s; + } + } + /** + * string chainName = 3; + * @return The bytes for chainName. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getChainNameBytes() { + java.lang.Object ref = chainName_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + chainName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (saplingActivation_ != 0) { + output.writeInt32(1, saplingActivation_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(branchID_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, branchID_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(chainName_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 3, chainName_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (saplingActivation_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, saplingActivation_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(branchID_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, branchID_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(chainName_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, chainName_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState other = (cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState) obj; + + if (getSaplingActivation() + != other.getSaplingActivation()) return false; + if (!getBranchID() + .equals(other.getBranchID())) return false; + if (!getChainName() + .equals(other.getChainName())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + SAPLINGACTIVATION_FIELD_NUMBER; + hash = (53 * hash) + getSaplingActivation(); + hash = (37 * hash) + BRANCHID_FIELD_NUMBER; + hash = (53 * hash) + getBranchID().hashCode(); + hash = (37 * hash) + CHAINNAME_FIELD_NUMBER; + hash = (53 * hash) + getChainName().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideMetaState} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.DarksideMetaState) + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaStateOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.class, cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + saplingActivation_ = 0; + + branchID_ = ""; + + chainName_ = ""; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState build() { + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState buildPartial() { + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState result = new cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState(this); + result.saplingActivation_ = saplingActivation_; + result.branchID_ = branchID_; + result.chainName_ = chainName_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState) { + return mergeFrom((cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState other) { + if (other == cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.getDefaultInstance()) return this; + if (other.getSaplingActivation() != 0) { + setSaplingActivation(other.getSaplingActivation()); + } + if (!other.getBranchID().isEmpty()) { + branchID_ = other.branchID_; + onChanged(); + } + if (!other.getChainName().isEmpty()) { + chainName_ = other.chainName_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int saplingActivation_ ; + /** + * int32 saplingActivation = 1; + * @return The saplingActivation. + */ + @java.lang.Override + public int getSaplingActivation() { + return saplingActivation_; + } + /** + * int32 saplingActivation = 1; + * @param value The saplingActivation to set. + * @return This builder for chaining. + */ + public Builder setSaplingActivation(int value) { + + saplingActivation_ = value; + onChanged(); + return this; + } + /** + * int32 saplingActivation = 1; + * @return This builder for chaining. + */ + public Builder clearSaplingActivation() { + + saplingActivation_ = 0; + onChanged(); + return this; + } + + private java.lang.Object branchID_ = ""; + /** + * string branchID = 2; + * @return The branchID. + */ + public java.lang.String getBranchID() { + java.lang.Object ref = branchID_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + branchID_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string branchID = 2; + * @return The bytes for branchID. + */ + public com.google.protobuf.ByteString + getBranchIDBytes() { + java.lang.Object ref = branchID_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + branchID_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string branchID = 2; + * @param value The branchID to set. + * @return This builder for chaining. + */ + public Builder setBranchID( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + branchID_ = value; + onChanged(); + return this; + } + /** + * string branchID = 2; + * @return This builder for chaining. + */ + public Builder clearBranchID() { + + branchID_ = getDefaultInstance().getBranchID(); + onChanged(); + return this; + } + /** + * string branchID = 2; + * @param value The bytes for branchID to set. + * @return This builder for chaining. + */ + public Builder setBranchIDBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + branchID_ = value; + onChanged(); + return this; + } + + private java.lang.Object chainName_ = ""; + /** + * string chainName = 3; + * @return The chainName. + */ + public java.lang.String getChainName() { + java.lang.Object ref = chainName_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + chainName_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string chainName = 3; + * @return The bytes for chainName. + */ + public com.google.protobuf.ByteString + getChainNameBytes() { + java.lang.Object ref = chainName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + chainName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string chainName = 3; + * @param value The chainName to set. + * @return This builder for chaining. + */ + public Builder setChainName( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + chainName_ = value; + onChanged(); + return this; + } + /** + * string chainName = 3; + * @return This builder for chaining. + */ + public Builder clearChainName() { + + chainName_ = getDefaultInstance().getChainName(); + onChanged(); + return this; + } + /** + * string chainName = 3; + * @param value The bytes for chainName to set. + * @return This builder for chaining. + */ + public Builder setChainNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + chainName_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.DarksideMetaState) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.DarksideMetaState) + private static final cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState(); + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public DarksideMetaState parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new DarksideMetaState(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface DarksideBlockOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.DarksideBlock) + com.google.protobuf.MessageOrBuilder { + + /** + * string block = 1; + * @return The block. + */ + java.lang.String getBlock(); + /** + * string block = 1; + * @return The bytes for block. + */ + com.google.protobuf.ByteString + getBlockBytes(); + } + /** + *
+   * A block is a hex-encoded string.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideBlock} + */ + public static final class DarksideBlock extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.DarksideBlock) + DarksideBlockOrBuilder { + private static final long serialVersionUID = 0L; + // Use DarksideBlock.newBuilder() to construct. + private DarksideBlock(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private DarksideBlock() { + block_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new DarksideBlock(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private DarksideBlock( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + java.lang.String s = input.readStringRequireUtf8(); + + block_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.class, cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.Builder.class); + } + + public static final int BLOCK_FIELD_NUMBER = 1; + private volatile java.lang.Object block_; + /** + * string block = 1; + * @return The block. + */ + @java.lang.Override + public java.lang.String getBlock() { + java.lang.Object ref = block_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + block_ = s; + return s; + } + } + /** + * string block = 1; + * @return The bytes for block. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getBlockBytes() { + java.lang.Object ref = block_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + block_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(block_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, block_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(block_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, block_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideBlock)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Darkside.DarksideBlock other = (cash.z.wallet.sdk.rpc.Darkside.DarksideBlock) obj; + + if (!getBlock() + .equals(other.getBlock())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + BLOCK_FIELD_NUMBER; + hash = (53 * hash) + getBlock().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Darkside.DarksideBlock prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * A block is a hex-encoded string.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideBlock} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.DarksideBlock) + cash.z.wallet.sdk.rpc.Darkside.DarksideBlockOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.class, cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + block_ = ""; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideBlock getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideBlock build() { + cash.z.wallet.sdk.rpc.Darkside.DarksideBlock result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideBlock buildPartial() { + cash.z.wallet.sdk.rpc.Darkside.DarksideBlock result = new cash.z.wallet.sdk.rpc.Darkside.DarksideBlock(this); + result.block_ = block_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideBlock) { + return mergeFrom((cash.z.wallet.sdk.rpc.Darkside.DarksideBlock)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Darkside.DarksideBlock other) { + if (other == cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.getDefaultInstance()) return this; + if (!other.getBlock().isEmpty()) { + block_ = other.block_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Darkside.DarksideBlock parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Darkside.DarksideBlock) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private java.lang.Object block_ = ""; + /** + * string block = 1; + * @return The block. + */ + public java.lang.String getBlock() { + java.lang.Object ref = block_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + block_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string block = 1; + * @return The bytes for block. + */ + public com.google.protobuf.ByteString + getBlockBytes() { + java.lang.Object ref = block_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + block_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string block = 1; + * @param value The block to set. + * @return This builder for chaining. + */ + public Builder setBlock( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + block_ = value; + onChanged(); + return this; + } + /** + * string block = 1; + * @return This builder for chaining. + */ + public Builder clearBlock() { + + block_ = getDefaultInstance().getBlock(); + onChanged(); + return this; + } + /** + * string block = 1; + * @param value The bytes for block to set. + * @return This builder for chaining. + */ + public Builder setBlockBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + block_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.DarksideBlock) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.DarksideBlock) + private static final cash.z.wallet.sdk.rpc.Darkside.DarksideBlock DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Darkside.DarksideBlock(); + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlock getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public DarksideBlock parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new DarksideBlock(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideBlock getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface DarksideBlocksURLOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.DarksideBlocksURL) + com.google.protobuf.MessageOrBuilder { + + /** + * string url = 1; + * @return The url. + */ + java.lang.String getUrl(); + /** + * string url = 1; + * @return The bytes for url. + */ + com.google.protobuf.ByteString + getUrlBytes(); + } + /** + *
+   * DarksideBlocksURL is typically something like:
+   * https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideBlocksURL} + */ + public static final class DarksideBlocksURL extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.DarksideBlocksURL) + DarksideBlocksURLOrBuilder { + private static final long serialVersionUID = 0L; + // Use DarksideBlocksURL.newBuilder() to construct. + private DarksideBlocksURL(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private DarksideBlocksURL() { + url_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new DarksideBlocksURL(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private DarksideBlocksURL( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + java.lang.String s = input.readStringRequireUtf8(); + + url_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.class, cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.Builder.class); + } + + public static final int URL_FIELD_NUMBER = 1; + private volatile java.lang.Object url_; + /** + * string url = 1; + * @return The url. + */ + @java.lang.Override + public java.lang.String getUrl() { + java.lang.Object ref = url_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + url_ = s; + return s; + } + } + /** + * string url = 1; + * @return The bytes for url. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getUrlBytes() { + java.lang.Object ref = url_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + url_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(url_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, url_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(url_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, url_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL other = (cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL) obj; + + if (!getUrl() + .equals(other.getUrl())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + URL_FIELD_NUMBER; + hash = (53 * hash) + getUrl().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * DarksideBlocksURL is typically something like:
+     * https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideBlocksURL} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.DarksideBlocksURL) + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURLOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.class, cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + url_ = ""; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL build() { + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL buildPartial() { + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL result = new cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL(this); + result.url_ = url_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL) { + return mergeFrom((cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL other) { + if (other == cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.getDefaultInstance()) return this; + if (!other.getUrl().isEmpty()) { + url_ = other.url_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private java.lang.Object url_ = ""; + /** + * string url = 1; + * @return The url. + */ + public java.lang.String getUrl() { + java.lang.Object ref = url_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + url_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string url = 1; + * @return The bytes for url. + */ + public com.google.protobuf.ByteString + getUrlBytes() { + java.lang.Object ref = url_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + url_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string url = 1; + * @param value The url to set. + * @return This builder for chaining. + */ + public Builder setUrl( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + url_ = value; + onChanged(); + return this; + } + /** + * string url = 1; + * @return This builder for chaining. + */ + public Builder clearUrl() { + + url_ = getDefaultInstance().getUrl(); + onChanged(); + return this; + } + /** + * string url = 1; + * @param value The bytes for url to set. + * @return This builder for chaining. + */ + public Builder setUrlBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + url_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.DarksideBlocksURL) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.DarksideBlocksURL) + private static final cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL(); + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public DarksideBlocksURL parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new DarksideBlocksURL(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface DarksideTransactionsURLOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.DarksideTransactionsURL) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 height = 1; + * @return The height. + */ + int getHeight(); + + /** + * string url = 2; + * @return The url. + */ + java.lang.String getUrl(); + /** + * string url = 2; + * @return The bytes for url. + */ + com.google.protobuf.ByteString + getUrlBytes(); + } + /** + *
+   * DarksideTransactionsURL refers to an HTTP source that contains a list
+   * of hex-encoded transactions, one per line, that are to be associated
+   * with the given height (fake-mined into the block at that height)
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideTransactionsURL} + */ + public static final class DarksideTransactionsURL extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.DarksideTransactionsURL) + DarksideTransactionsURLOrBuilder { + private static final long serialVersionUID = 0L; + // Use DarksideTransactionsURL.newBuilder() to construct. + private DarksideTransactionsURL(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private DarksideTransactionsURL() { + url_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new DarksideTransactionsURL(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private DarksideTransactionsURL( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + height_ = input.readInt32(); + break; + } + case 18: { + java.lang.String s = input.readStringRequireUtf8(); + + url_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.class, cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.Builder.class); + } + + public static final int HEIGHT_FIELD_NUMBER = 1; + private int height_; + /** + * int32 height = 1; + * @return The height. + */ + @java.lang.Override + public int getHeight() { + return height_; + } + + public static final int URL_FIELD_NUMBER = 2; + private volatile java.lang.Object url_; + /** + * string url = 2; + * @return The url. + */ + @java.lang.Override + public java.lang.String getUrl() { + java.lang.Object ref = url_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + url_ = s; + return s; + } + } + /** + * string url = 2; + * @return The bytes for url. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getUrlBytes() { + java.lang.Object ref = url_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + url_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (height_ != 0) { + output.writeInt32(1, height_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(url_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, url_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (height_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, height_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(url_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, url_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL other = (cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL) obj; + + if (getHeight() + != other.getHeight()) return false; + if (!getUrl() + .equals(other.getUrl())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + HEIGHT_FIELD_NUMBER; + hash = (53 * hash) + getHeight(); + hash = (37 * hash) + URL_FIELD_NUMBER; + hash = (53 * hash) + getUrl().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * DarksideTransactionsURL refers to an HTTP source that contains a list
+     * of hex-encoded transactions, one per line, that are to be associated
+     * with the given height (fake-mined into the block at that height)
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideTransactionsURL} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.DarksideTransactionsURL) + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURLOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.class, cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + height_ = 0; + + url_ = ""; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL build() { + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL buildPartial() { + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL result = new cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL(this); + result.height_ = height_; + result.url_ = url_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL) { + return mergeFrom((cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL other) { + if (other == cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.getDefaultInstance()) return this; + if (other.getHeight() != 0) { + setHeight(other.getHeight()); + } + if (!other.getUrl().isEmpty()) { + url_ = other.url_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int height_ ; + /** + * int32 height = 1; + * @return The height. + */ + @java.lang.Override + public int getHeight() { + return height_; + } + /** + * int32 height = 1; + * @param value The height to set. + * @return This builder for chaining. + */ + public Builder setHeight(int value) { + + height_ = value; + onChanged(); + return this; + } + /** + * int32 height = 1; + * @return This builder for chaining. + */ + public Builder clearHeight() { + + height_ = 0; + onChanged(); + return this; + } + + private java.lang.Object url_ = ""; + /** + * string url = 2; + * @return The url. + */ + public java.lang.String getUrl() { + java.lang.Object ref = url_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + url_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string url = 2; + * @return The bytes for url. + */ + public com.google.protobuf.ByteString + getUrlBytes() { + java.lang.Object ref = url_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + url_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string url = 2; + * @param value The url to set. + * @return This builder for chaining. + */ + public Builder setUrl( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + url_ = value; + onChanged(); + return this; + } + /** + * string url = 2; + * @return This builder for chaining. + */ + public Builder clearUrl() { + + url_ = getDefaultInstance().getUrl(); + onChanged(); + return this; + } + /** + * string url = 2; + * @param value The bytes for url to set. + * @return This builder for chaining. + */ + public Builder setUrlBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + url_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.DarksideTransactionsURL) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.DarksideTransactionsURL) + private static final cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL(); + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public DarksideTransactionsURL parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new DarksideTransactionsURL(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface DarksideHeightOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.DarksideHeight) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 height = 1; + * @return The height. + */ + int getHeight(); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideHeight} + */ + public static final class DarksideHeight extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.DarksideHeight) + DarksideHeightOrBuilder { + private static final long serialVersionUID = 0L; + // Use DarksideHeight.newBuilder() to construct. + private DarksideHeight(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private DarksideHeight() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new DarksideHeight(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private DarksideHeight( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + height_ = input.readInt32(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.class, cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.Builder.class); + } + + public static final int HEIGHT_FIELD_NUMBER = 1; + private int height_; + /** + * int32 height = 1; + * @return The height. + */ + @java.lang.Override + public int getHeight() { + return height_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (height_ != 0) { + output.writeInt32(1, height_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (height_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, height_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideHeight)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight other = (cash.z.wallet.sdk.rpc.Darkside.DarksideHeight) obj; + + if (getHeight() + != other.getHeight()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + HEIGHT_FIELD_NUMBER; + hash = (53 * hash) + getHeight(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Darkside.DarksideHeight prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideHeight} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.DarksideHeight) + cash.z.wallet.sdk.rpc.Darkside.DarksideHeightOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.class, cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + height_ = 0; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideHeight getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideHeight build() { + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideHeight buildPartial() { + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight result = new cash.z.wallet.sdk.rpc.Darkside.DarksideHeight(this); + result.height_ = height_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideHeight) { + return mergeFrom((cash.z.wallet.sdk.rpc.Darkside.DarksideHeight)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Darkside.DarksideHeight other) { + if (other == cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.getDefaultInstance()) return this; + if (other.getHeight() != 0) { + setHeight(other.getHeight()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Darkside.DarksideHeight) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int height_ ; + /** + * int32 height = 1; + * @return The height. + */ + @java.lang.Override + public int getHeight() { + return height_; + } + /** + * int32 height = 1; + * @param value The height to set. + * @return This builder for chaining. + */ + public Builder setHeight(int value) { + + height_ = value; + onChanged(); + return this; + } + /** + * int32 height = 1; + * @return This builder for chaining. + */ + public Builder clearHeight() { + + height_ = 0; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.DarksideHeight) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.DarksideHeight) + private static final cash.z.wallet.sdk.rpc.Darkside.DarksideHeight DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Darkside.DarksideHeight(); + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideHeight getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public DarksideHeight parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new DarksideHeight(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideHeight getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface DarksideEmptyBlocksOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.DarksideEmptyBlocks) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 height = 1; + * @return The height. + */ + int getHeight(); + + /** + * int32 nonce = 2; + * @return The nonce. + */ + int getNonce(); + + /** + * int32 count = 3; + * @return The count. + */ + int getCount(); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideEmptyBlocks} + */ + public static final class DarksideEmptyBlocks extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.DarksideEmptyBlocks) + DarksideEmptyBlocksOrBuilder { + private static final long serialVersionUID = 0L; + // Use DarksideEmptyBlocks.newBuilder() to construct. + private DarksideEmptyBlocks(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private DarksideEmptyBlocks() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new DarksideEmptyBlocks(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private DarksideEmptyBlocks( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + height_ = input.readInt32(); + break; + } + case 16: { + + nonce_ = input.readInt32(); + break; + } + case 24: { + + count_ = input.readInt32(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.class, cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.Builder.class); + } + + public static final int HEIGHT_FIELD_NUMBER = 1; + private int height_; + /** + * int32 height = 1; + * @return The height. + */ + @java.lang.Override + public int getHeight() { + return height_; + } + + public static final int NONCE_FIELD_NUMBER = 2; + private int nonce_; + /** + * int32 nonce = 2; + * @return The nonce. + */ + @java.lang.Override + public int getNonce() { + return nonce_; + } + + public static final int COUNT_FIELD_NUMBER = 3; + private int count_; + /** + * int32 count = 3; + * @return The count. + */ + @java.lang.Override + public int getCount() { + return count_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (height_ != 0) { + output.writeInt32(1, height_); + } + if (nonce_ != 0) { + output.writeInt32(2, nonce_); + } + if (count_ != 0) { + output.writeInt32(3, count_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (height_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, height_); + } + if (nonce_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(2, nonce_); + } + if (count_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(3, count_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks other = (cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks) obj; + + if (getHeight() + != other.getHeight()) return false; + if (getNonce() + != other.getNonce()) return false; + if (getCount() + != other.getCount()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + HEIGHT_FIELD_NUMBER; + hash = (53 * hash) + getHeight(); + hash = (37 * hash) + NONCE_FIELD_NUMBER; + hash = (53 * hash) + getNonce(); + hash = (37 * hash) + COUNT_FIELD_NUMBER; + hash = (53 * hash) + getCount(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.DarksideEmptyBlocks} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.DarksideEmptyBlocks) + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocksOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.class, cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + height_ = 0; + + nonce_ = 0; + + count_ = 0; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Darkside.internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks build() { + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks buildPartial() { + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks result = new cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks(this); + result.height_ = height_; + result.nonce_ = nonce_; + result.count_ = count_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks) { + return mergeFrom((cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks other) { + if (other == cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.getDefaultInstance()) return this; + if (other.getHeight() != 0) { + setHeight(other.getHeight()); + } + if (other.getNonce() != 0) { + setNonce(other.getNonce()); + } + if (other.getCount() != 0) { + setCount(other.getCount()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int height_ ; + /** + * int32 height = 1; + * @return The height. + */ + @java.lang.Override + public int getHeight() { + return height_; + } + /** + * int32 height = 1; + * @param value The height to set. + * @return This builder for chaining. + */ + public Builder setHeight(int value) { + + height_ = value; + onChanged(); + return this; + } + /** + * int32 height = 1; + * @return This builder for chaining. + */ + public Builder clearHeight() { + + height_ = 0; + onChanged(); + return this; + } + + private int nonce_ ; + /** + * int32 nonce = 2; + * @return The nonce. + */ + @java.lang.Override + public int getNonce() { + return nonce_; + } + /** + * int32 nonce = 2; + * @param value The nonce to set. + * @return This builder for chaining. + */ + public Builder setNonce(int value) { + + nonce_ = value; + onChanged(); + return this; + } + /** + * int32 nonce = 2; + * @return This builder for chaining. + */ + public Builder clearNonce() { + + nonce_ = 0; + onChanged(); + return this; + } + + private int count_ ; + /** + * int32 count = 3; + * @return The count. + */ + @java.lang.Override + public int getCount() { + return count_; + } + /** + * int32 count = 3; + * @param value The count to set. + * @return This builder for chaining. + */ + public Builder setCount(int value) { + + count_ = value; + onChanged(); + return this; + } + /** + * int32 count = 3; + * @return This builder for chaining. + */ + public Builder clearCount() { + + count_ = 0; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.DarksideEmptyBlocks) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.DarksideEmptyBlocks) + private static final cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks(); + } + + public static cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public DarksideEmptyBlocks parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new DarksideEmptyBlocks(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\016darkside.proto\022\025cash.z.wallet.sdk.rpc\032" + + "\rservice.proto\"S\n\021DarksideMetaState\022\031\n\021s" + + "aplingActivation\030\001 \001(\005\022\020\n\010branchID\030\002 \001(\t" + + "\022\021\n\tchainName\030\003 \001(\t\"\036\n\rDarksideBlock\022\r\n\005" + + "block\030\001 \001(\t\" \n\021DarksideBlocksURL\022\013\n\003url\030" + + "\001 \001(\t\"6\n\027DarksideTransactionsURL\022\016\n\006heig" + + "ht\030\001 \001(\005\022\013\n\003url\030\002 \001(\t\" \n\016DarksideHeight\022" + + "\016\n\006height\030\001 \001(\005\"C\n\023DarksideEmptyBlocks\022\016" + + "\n\006height\030\001 \001(\005\022\r\n\005nonce\030\002 \001(\005\022\r\n\005count\030\003" + + " \001(\0052\332\006\n\020DarksideStreamer\022Q\n\005Reset\022(.cas" + + "h.z.wallet.sdk.rpc.DarksideMetaState\032\034.c" + + "ash.z.wallet.sdk.rpc.Empty\"\000\022[\n\021StageBlo" + + "cksStream\022$.cash.z.wallet.sdk.rpc.Darksi" + + "deBlock\032\034.cash.z.wallet.sdk.rpc.Empty\"\000(" + + "\001\022W\n\013StageBlocks\022(.cash.z.wallet.sdk.rpc" + + ".DarksideBlocksURL\032\034.cash.z.wallet.sdk.r" + + "pc.Empty\"\000\022_\n\021StageBlocksCreate\022*.cash.z" + + ".wallet.sdk.rpc.DarksideEmptyBlocks\032\034.ca" + + "sh.z.wallet.sdk.rpc.Empty\"\000\022b\n\027StageTran" + + "sactionsStream\022%.cash.z.wallet.sdk.rpc.R" + + "awTransaction\032\034.cash.z.wallet.sdk.rpc.Em" + + "pty\"\000(\001\022c\n\021StageTransactions\022..cash.z.wa" + + "llet.sdk.rpc.DarksideTransactionsURL\032\034.c" + + "ash.z.wallet.sdk.rpc.Empty\"\000\022T\n\013ApplySta" + + "ged\022%.cash.z.wallet.sdk.rpc.DarksideHeig" + + "ht\032\034.cash.z.wallet.sdk.rpc.Empty\"\000\022b\n\027Ge" + + "tIncomingTransactions\022\034.cash.z.wallet.sd" + + "k.rpc.Empty\032%.cash.z.wallet.sdk.rpc.RawT" + + "ransaction\"\0000\001\022Y\n\031ClearIncomingTransacti" + + "ons\022\034.cash.z.wallet.sdk.rpc.Empty\032\034.cash" + + ".z.wallet.sdk.rpc.Empty\"\000B\033Z\026lightwallet" + + "d/walletrpc\272\002\000b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + cash.z.wallet.sdk.rpc.Service.getDescriptor(), + }); + internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_DarksideMetaState_descriptor, + new java.lang.String[] { "SaplingActivation", "BranchID", "ChainName", }); + internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_DarksideBlock_descriptor, + new java.lang.String[] { "Block", }); + internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_DarksideBlocksURL_descriptor, + new java.lang.String[] { "Url", }); + internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_descriptor = + getDescriptor().getMessageTypes().get(3); + internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_DarksideTransactionsURL_descriptor, + new java.lang.String[] { "Height", "Url", }); + internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_descriptor = + getDescriptor().getMessageTypes().get(4); + internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_DarksideHeight_descriptor, + new java.lang.String[] { "Height", }); + internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_descriptor = + getDescriptor().getMessageTypes().get(5); + internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_DarksideEmptyBlocks_descriptor, + new java.lang.String[] { "Height", "Nonce", "Count", }); + cash.z.wallet.sdk.rpc.Service.getDescriptor(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/src/main/java/cash/z/wallet/sdk/rpc/DarksideStreamerGrpc.java b/src/main/java/cash/z/wallet/sdk/rpc/DarksideStreamerGrpc.java new file mode 100644 index 00000000..5b180bea --- /dev/null +++ b/src/main/java/cash/z/wallet/sdk/rpc/DarksideStreamerGrpc.java @@ -0,0 +1,1086 @@ +package cash.z.wallet.sdk.rpc; + +import static io.grpc.MethodDescriptor.generateFullMethodName; + +/** + *
+ * Darksidewalletd maintains two staging areas, blocks and transactions. The
+ * Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything
+ * in the staging area to the working (operational) state that the mock zcashd
+ * serves; transactions are placed into their corresponding blocks (by height).
+ * 
+ */ +@javax.annotation.Generated( + value = "by gRPC proto compiler (version 1.45.1)", + comments = "Source: darkside.proto") +@io.grpc.stub.annotations.GrpcGenerated +public final class DarksideStreamerGrpc { + + private DarksideStreamerGrpc() {} + + public static final String SERVICE_NAME = "cash.z.wallet.sdk.rpc.DarksideStreamer"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getResetMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "Reset", + requestType = cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.class, + responseType = cash.z.wallet.sdk.rpc.Service.Empty.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getResetMethod() { + io.grpc.MethodDescriptor getResetMethod; + if ((getResetMethod = DarksideStreamerGrpc.getResetMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getResetMethod = DarksideStreamerGrpc.getResetMethod) == null) { + DarksideStreamerGrpc.getResetMethod = getResetMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "Reset")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("Reset")) + .build(); + } + } + } + return getResetMethod; + } + + private static volatile io.grpc.MethodDescriptor getStageBlocksStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "StageBlocksStream", + requestType = cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.class, + responseType = cash.z.wallet.sdk.rpc.Service.Empty.class, + methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + public static io.grpc.MethodDescriptor getStageBlocksStreamMethod() { + io.grpc.MethodDescriptor getStageBlocksStreamMethod; + if ((getStageBlocksStreamMethod = DarksideStreamerGrpc.getStageBlocksStreamMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getStageBlocksStreamMethod = DarksideStreamerGrpc.getStageBlocksStreamMethod) == null) { + DarksideStreamerGrpc.getStageBlocksStreamMethod = getStageBlocksStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StageBlocksStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Darkside.DarksideBlock.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("StageBlocksStream")) + .build(); + } + } + } + return getStageBlocksStreamMethod; + } + + private static volatile io.grpc.MethodDescriptor getStageBlocksMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "StageBlocks", + requestType = cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.class, + responseType = cash.z.wallet.sdk.rpc.Service.Empty.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getStageBlocksMethod() { + io.grpc.MethodDescriptor getStageBlocksMethod; + if ((getStageBlocksMethod = DarksideStreamerGrpc.getStageBlocksMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getStageBlocksMethod = DarksideStreamerGrpc.getStageBlocksMethod) == null) { + DarksideStreamerGrpc.getStageBlocksMethod = getStageBlocksMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StageBlocks")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("StageBlocks")) + .build(); + } + } + } + return getStageBlocksMethod; + } + + private static volatile io.grpc.MethodDescriptor getStageBlocksCreateMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "StageBlocksCreate", + requestType = cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.class, + responseType = cash.z.wallet.sdk.rpc.Service.Empty.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getStageBlocksCreateMethod() { + io.grpc.MethodDescriptor getStageBlocksCreateMethod; + if ((getStageBlocksCreateMethod = DarksideStreamerGrpc.getStageBlocksCreateMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getStageBlocksCreateMethod = DarksideStreamerGrpc.getStageBlocksCreateMethod) == null) { + DarksideStreamerGrpc.getStageBlocksCreateMethod = getStageBlocksCreateMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StageBlocksCreate")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("StageBlocksCreate")) + .build(); + } + } + } + return getStageBlocksCreateMethod; + } + + private static volatile io.grpc.MethodDescriptor getStageTransactionsStreamMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "StageTransactionsStream", + requestType = cash.z.wallet.sdk.rpc.Service.RawTransaction.class, + responseType = cash.z.wallet.sdk.rpc.Service.Empty.class, + methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + public static io.grpc.MethodDescriptor getStageTransactionsStreamMethod() { + io.grpc.MethodDescriptor getStageTransactionsStreamMethod; + if ((getStageTransactionsStreamMethod = DarksideStreamerGrpc.getStageTransactionsStreamMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getStageTransactionsStreamMethod = DarksideStreamerGrpc.getStageTransactionsStreamMethod) == null) { + DarksideStreamerGrpc.getStageTransactionsStreamMethod = getStageTransactionsStreamMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StageTransactionsStream")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.RawTransaction.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("StageTransactionsStream")) + .build(); + } + } + } + return getStageTransactionsStreamMethod; + } + + private static volatile io.grpc.MethodDescriptor getStageTransactionsMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "StageTransactions", + requestType = cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.class, + responseType = cash.z.wallet.sdk.rpc.Service.Empty.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getStageTransactionsMethod() { + io.grpc.MethodDescriptor getStageTransactionsMethod; + if ((getStageTransactionsMethod = DarksideStreamerGrpc.getStageTransactionsMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getStageTransactionsMethod = DarksideStreamerGrpc.getStageTransactionsMethod) == null) { + DarksideStreamerGrpc.getStageTransactionsMethod = getStageTransactionsMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StageTransactions")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("StageTransactions")) + .build(); + } + } + } + return getStageTransactionsMethod; + } + + private static volatile io.grpc.MethodDescriptor getApplyStagedMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "ApplyStaged", + requestType = cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.class, + responseType = cash.z.wallet.sdk.rpc.Service.Empty.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getApplyStagedMethod() { + io.grpc.MethodDescriptor getApplyStagedMethod; + if ((getApplyStagedMethod = DarksideStreamerGrpc.getApplyStagedMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getApplyStagedMethod = DarksideStreamerGrpc.getApplyStagedMethod) == null) { + DarksideStreamerGrpc.getApplyStagedMethod = getApplyStagedMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "ApplyStaged")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("ApplyStaged")) + .build(); + } + } + } + return getApplyStagedMethod; + } + + private static volatile io.grpc.MethodDescriptor getGetIncomingTransactionsMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "GetIncomingTransactions", + requestType = cash.z.wallet.sdk.rpc.Service.Empty.class, + responseType = cash.z.wallet.sdk.rpc.Service.RawTransaction.class, + methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + public static io.grpc.MethodDescriptor getGetIncomingTransactionsMethod() { + io.grpc.MethodDescriptor getGetIncomingTransactionsMethod; + if ((getGetIncomingTransactionsMethod = DarksideStreamerGrpc.getGetIncomingTransactionsMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getGetIncomingTransactionsMethod = DarksideStreamerGrpc.getGetIncomingTransactionsMethod) == null) { + DarksideStreamerGrpc.getGetIncomingTransactionsMethod = getGetIncomingTransactionsMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "GetIncomingTransactions")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.RawTransaction.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("GetIncomingTransactions")) + .build(); + } + } + } + return getGetIncomingTransactionsMethod; + } + + private static volatile io.grpc.MethodDescriptor getClearIncomingTransactionsMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "ClearIncomingTransactions", + requestType = cash.z.wallet.sdk.rpc.Service.Empty.class, + responseType = cash.z.wallet.sdk.rpc.Service.Empty.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getClearIncomingTransactionsMethod() { + io.grpc.MethodDescriptor getClearIncomingTransactionsMethod; + if ((getClearIncomingTransactionsMethod = DarksideStreamerGrpc.getClearIncomingTransactionsMethod) == null) { + synchronized (DarksideStreamerGrpc.class) { + if ((getClearIncomingTransactionsMethod = DarksideStreamerGrpc.getClearIncomingTransactionsMethod) == null) { + DarksideStreamerGrpc.getClearIncomingTransactionsMethod = getClearIncomingTransactionsMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "ClearIncomingTransactions")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance())) + .setSchemaDescriptor(new DarksideStreamerMethodDescriptorSupplier("ClearIncomingTransactions")) + .build(); + } + } + } + return getClearIncomingTransactionsMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static DarksideStreamerStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public DarksideStreamerStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new DarksideStreamerStub(channel, callOptions); + } + }; + return DarksideStreamerStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static DarksideStreamerBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public DarksideStreamerBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new DarksideStreamerBlockingStub(channel, callOptions); + } + }; + return DarksideStreamerBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static DarksideStreamerFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public DarksideStreamerFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new DarksideStreamerFutureStub(channel, callOptions); + } + }; + return DarksideStreamerFutureStub.newStub(factory, channel); + } + + /** + *
+   * Darksidewalletd maintains two staging areas, blocks and transactions. The
+   * Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything
+   * in the staging area to the working (operational) state that the mock zcashd
+   * serves; transactions are placed into their corresponding blocks (by height).
+   * 
+ */ + public static abstract class DarksideStreamerImplBase implements io.grpc.BindableService { + + /** + *
+     * Reset reverts all darksidewalletd state (active block range, latest height,
+     * staged blocks and transactions) and lightwalletd state (cache) to empty,
+     * the same as the initial state. This occurs synchronously and instantaneously;
+     * no reorg happens in lightwalletd. This is good to do before each independent
+     * test so that no state leaks from one test to another.
+     * Also sets (some of) the values returned by GetLightdInfo(). The Sapling
+     * activation height specified here must be where the block range starts.
+     * 
+ */ + public void reset(cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getResetMethod(), responseObserver); + } + + /** + *
+     * StageBlocksStream accepts a list of blocks and saves them into the blocks
+     * staging area until ApplyStaged() is called; there is no immediate effect on
+     * the mock zcashd. Blocks are hex-encoded. Order is important, see ApplyStaged.
+     * 
+ */ + public io.grpc.stub.StreamObserver stageBlocksStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getStageBlocksStreamMethod(), responseObserver); + } + + /** + *
+     * StageBlocks is the same as StageBlocksStream() except the blocks are fetched
+     * from the given URL. Blocks are one per line, hex-encoded (not JSON).
+     * 
+ */ + public void stageBlocks(cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getStageBlocksMethod(), responseObserver); + } + + /** + *
+     * StageBlocksCreate is like the previous two, except it creates 'count'
+     * empty blocks at consecutive heights starting at height 'height'. The
+     * 'nonce' is part of the header, so it contributes to the block hash; this
+     * lets you create identical blocks (same transactions and height), but with
+     * different hashes.
+     * 
+ */ + public void stageBlocksCreate(cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getStageBlocksCreateMethod(), responseObserver); + } + + /** + *
+     * StageTransactionsStream stores the given transaction-height pairs in the
+     * staging area until ApplyStaged() is called. Note that these transactions
+     * are not returned by the production GetTransaction() gRPC until they
+     * appear in a "mined" block (contained in the active blockchain presented
+     * by the mock zcashd).
+     * 
+ */ + public io.grpc.stub.StreamObserver stageTransactionsStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getStageTransactionsStreamMethod(), responseObserver); + } + + /** + *
+     * StageTransactions is the same except the transactions are fetched from
+     * the given url. They are all staged into the block at the given height.
+     * Staging transactions to different heights requires multiple calls.
+     * 
+ */ + public void stageTransactions(cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getStageTransactionsMethod(), responseObserver); + } + + /** + *
+     * ApplyStaged iterates the list of blocks that were staged by the
+     * StageBlocks*() gRPCs, in the order they were staged, and "merges" each
+     * into the active, working blocks list that the mock zcashd is presenting
+     * to lightwalletd. Even as each block is applied, the active list can't
+     * have gaps; if the active block range is 1000-1006, and the staged block
+     * range is 1003-1004, the resulting range is 1000-1004, with 1000-1002
+     * unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped.
+     * After merging all blocks, ApplyStaged() appends staged transactions (in
+     * the order received) into each one's corresponding (by height) block
+     * The staging area is then cleared.
+     * The argument specifies the latest block height that mock zcashd reports
+     * (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
+     * also be used to simply advance the latest block height presented by mock
+     * zcashd. That is, there doesn't need to be anything in the staging area.
+     * 
+ */ + public void applyStaged(cash.z.wallet.sdk.rpc.Darkside.DarksideHeight request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getApplyStagedMethod(), responseObserver); + } + + /** + *
+     * Calls to the production gRPC SendTransaction() store the transaction in
+     * a separate area (not the staging area); this method returns all transactions
+     * in this separate area, which is then cleared. The height returned
+     * with each transaction is -1 (invalid) since these transactions haven't
+     * been mined yet. The intention is that the transactions returned here can
+     * then, for example, be given to StageTransactions() to get them "mined"
+     * into a specified block on the next ApplyStaged().
+     * 
+ */ + public void getIncomingTransactions(cash.z.wallet.sdk.rpc.Service.Empty request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getGetIncomingTransactionsMethod(), responseObserver); + } + + /** + *
+     * Clear the incoming transaction pool.
+     * 
+ */ + public void clearIncomingTransactions(cash.z.wallet.sdk.rpc.Service.Empty request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getClearIncomingTransactionsMethod(), responseObserver); + } + + @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getResetMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState, + cash.z.wallet.sdk.rpc.Service.Empty>( + this, METHODID_RESET))) + .addMethod( + getStageBlocksStreamMethod(), + io.grpc.stub.ServerCalls.asyncClientStreamingCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Darkside.DarksideBlock, + cash.z.wallet.sdk.rpc.Service.Empty>( + this, METHODID_STAGE_BLOCKS_STREAM))) + .addMethod( + getStageBlocksMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL, + cash.z.wallet.sdk.rpc.Service.Empty>( + this, METHODID_STAGE_BLOCKS))) + .addMethod( + getStageBlocksCreateMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks, + cash.z.wallet.sdk.rpc.Service.Empty>( + this, METHODID_STAGE_BLOCKS_CREATE))) + .addMethod( + getStageTransactionsStreamMethod(), + io.grpc.stub.ServerCalls.asyncClientStreamingCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.RawTransaction, + cash.z.wallet.sdk.rpc.Service.Empty>( + this, METHODID_STAGE_TRANSACTIONS_STREAM))) + .addMethod( + getStageTransactionsMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL, + cash.z.wallet.sdk.rpc.Service.Empty>( + this, METHODID_STAGE_TRANSACTIONS))) + .addMethod( + getApplyStagedMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight, + cash.z.wallet.sdk.rpc.Service.Empty>( + this, METHODID_APPLY_STAGED))) + .addMethod( + getGetIncomingTransactionsMethod(), + io.grpc.stub.ServerCalls.asyncServerStreamingCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.Empty, + cash.z.wallet.sdk.rpc.Service.RawTransaction>( + this, METHODID_GET_INCOMING_TRANSACTIONS))) + .addMethod( + getClearIncomingTransactionsMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + cash.z.wallet.sdk.rpc.Service.Empty, + cash.z.wallet.sdk.rpc.Service.Empty>( + this, METHODID_CLEAR_INCOMING_TRANSACTIONS))) + .build(); + } + } + + /** + *
+   * Darksidewalletd maintains two staging areas, blocks and transactions. The
+   * Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything
+   * in the staging area to the working (operational) state that the mock zcashd
+   * serves; transactions are placed into their corresponding blocks (by height).
+   * 
+ */ + public static final class DarksideStreamerStub extends io.grpc.stub.AbstractAsyncStub { + private DarksideStreamerStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected DarksideStreamerStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new DarksideStreamerStub(channel, callOptions); + } + + /** + *
+     * Reset reverts all darksidewalletd state (active block range, latest height,
+     * staged blocks and transactions) and lightwalletd state (cache) to empty,
+     * the same as the initial state. This occurs synchronously and instantaneously;
+     * no reorg happens in lightwalletd. This is good to do before each independent
+     * test so that no state leaks from one test to another.
+     * Also sets (some of) the values returned by GetLightdInfo(). The Sapling
+     * activation height specified here must be where the block range starts.
+     * 
+ */ + public void reset(cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getResetMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * StageBlocksStream accepts a list of blocks and saves them into the blocks
+     * staging area until ApplyStaged() is called; there is no immediate effect on
+     * the mock zcashd. Blocks are hex-encoded. Order is important, see ApplyStaged.
+     * 
+ */ + public io.grpc.stub.StreamObserver stageBlocksStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncClientStreamingCall( + getChannel().newCall(getStageBlocksStreamMethod(), getCallOptions()), responseObserver); + } + + /** + *
+     * StageBlocks is the same as StageBlocksStream() except the blocks are fetched
+     * from the given URL. Blocks are one per line, hex-encoded (not JSON).
+     * 
+ */ + public void stageBlocks(cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getStageBlocksMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * StageBlocksCreate is like the previous two, except it creates 'count'
+     * empty blocks at consecutive heights starting at height 'height'. The
+     * 'nonce' is part of the header, so it contributes to the block hash; this
+     * lets you create identical blocks (same transactions and height), but with
+     * different hashes.
+     * 
+ */ + public void stageBlocksCreate(cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getStageBlocksCreateMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * StageTransactionsStream stores the given transaction-height pairs in the
+     * staging area until ApplyStaged() is called. Note that these transactions
+     * are not returned by the production GetTransaction() gRPC until they
+     * appear in a "mined" block (contained in the active blockchain presented
+     * by the mock zcashd).
+     * 
+ */ + public io.grpc.stub.StreamObserver stageTransactionsStream( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncClientStreamingCall( + getChannel().newCall(getStageTransactionsStreamMethod(), getCallOptions()), responseObserver); + } + + /** + *
+     * StageTransactions is the same except the transactions are fetched from
+     * the given url. They are all staged into the block at the given height.
+     * Staging transactions to different heights requires multiple calls.
+     * 
+ */ + public void stageTransactions(cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getStageTransactionsMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * ApplyStaged iterates the list of blocks that were staged by the
+     * StageBlocks*() gRPCs, in the order they were staged, and "merges" each
+     * into the active, working blocks list that the mock zcashd is presenting
+     * to lightwalletd. Even as each block is applied, the active list can't
+     * have gaps; if the active block range is 1000-1006, and the staged block
+     * range is 1003-1004, the resulting range is 1000-1004, with 1000-1002
+     * unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped.
+     * After merging all blocks, ApplyStaged() appends staged transactions (in
+     * the order received) into each one's corresponding (by height) block
+     * The staging area is then cleared.
+     * The argument specifies the latest block height that mock zcashd reports
+     * (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
+     * also be used to simply advance the latest block height presented by mock
+     * zcashd. That is, there doesn't need to be anything in the staging area.
+     * 
+ */ + public void applyStaged(cash.z.wallet.sdk.rpc.Darkside.DarksideHeight request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getApplyStagedMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Calls to the production gRPC SendTransaction() store the transaction in
+     * a separate area (not the staging area); this method returns all transactions
+     * in this separate area, which is then cleared. The height returned
+     * with each transaction is -1 (invalid) since these transactions haven't
+     * been mined yet. The intention is that the transactions returned here can
+     * then, for example, be given to StageTransactions() to get them "mined"
+     * into a specified block on the next ApplyStaged().
+     * 
+ */ + public void getIncomingTransactions(cash.z.wallet.sdk.rpc.Service.Empty request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncServerStreamingCall( + getChannel().newCall(getGetIncomingTransactionsMethod(), getCallOptions()), request, responseObserver); + } + + /** + *
+     * Clear the incoming transaction pool.
+     * 
+ */ + public void clearIncomingTransactions(cash.z.wallet.sdk.rpc.Service.Empty request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getClearIncomingTransactionsMethod(), getCallOptions()), request, responseObserver); + } + } + + /** + *
+   * Darksidewalletd maintains two staging areas, blocks and transactions. The
+   * Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything
+   * in the staging area to the working (operational) state that the mock zcashd
+   * serves; transactions are placed into their corresponding blocks (by height).
+   * 
+ */ + public static final class DarksideStreamerBlockingStub extends io.grpc.stub.AbstractBlockingStub { + private DarksideStreamerBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected DarksideStreamerBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new DarksideStreamerBlockingStub(channel, callOptions); + } + + /** + *
+     * Reset reverts all darksidewalletd state (active block range, latest height,
+     * staged blocks and transactions) and lightwalletd state (cache) to empty,
+     * the same as the initial state. This occurs synchronously and instantaneously;
+     * no reorg happens in lightwalletd. This is good to do before each independent
+     * test so that no state leaks from one test to another.
+     * Also sets (some of) the values returned by GetLightdInfo(). The Sapling
+     * activation height specified here must be where the block range starts.
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.Empty reset(cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getResetMethod(), getCallOptions(), request); + } + + /** + *
+     * StageBlocks is the same as StageBlocksStream() except the blocks are fetched
+     * from the given URL. Blocks are one per line, hex-encoded (not JSON).
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.Empty stageBlocks(cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getStageBlocksMethod(), getCallOptions(), request); + } + + /** + *
+     * StageBlocksCreate is like the previous two, except it creates 'count'
+     * empty blocks at consecutive heights starting at height 'height'. The
+     * 'nonce' is part of the header, so it contributes to the block hash; this
+     * lets you create identical blocks (same transactions and height), but with
+     * different hashes.
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.Empty stageBlocksCreate(cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getStageBlocksCreateMethod(), getCallOptions(), request); + } + + /** + *
+     * StageTransactions is the same except the transactions are fetched from
+     * the given url. They are all staged into the block at the given height.
+     * Staging transactions to different heights requires multiple calls.
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.Empty stageTransactions(cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getStageTransactionsMethod(), getCallOptions(), request); + } + + /** + *
+     * ApplyStaged iterates the list of blocks that were staged by the
+     * StageBlocks*() gRPCs, in the order they were staged, and "merges" each
+     * into the active, working blocks list that the mock zcashd is presenting
+     * to lightwalletd. Even as each block is applied, the active list can't
+     * have gaps; if the active block range is 1000-1006, and the staged block
+     * range is 1003-1004, the resulting range is 1000-1004, with 1000-1002
+     * unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped.
+     * After merging all blocks, ApplyStaged() appends staged transactions (in
+     * the order received) into each one's corresponding (by height) block
+     * The staging area is then cleared.
+     * The argument specifies the latest block height that mock zcashd reports
+     * (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
+     * also be used to simply advance the latest block height presented by mock
+     * zcashd. That is, there doesn't need to be anything in the staging area.
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.Empty applyStaged(cash.z.wallet.sdk.rpc.Darkside.DarksideHeight request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getApplyStagedMethod(), getCallOptions(), request); + } + + /** + *
+     * Calls to the production gRPC SendTransaction() store the transaction in
+     * a separate area (not the staging area); this method returns all transactions
+     * in this separate area, which is then cleared. The height returned
+     * with each transaction is -1 (invalid) since these transactions haven't
+     * been mined yet. The intention is that the transactions returned here can
+     * then, for example, be given to StageTransactions() to get them "mined"
+     * into a specified block on the next ApplyStaged().
+     * 
+ */ + public java.util.Iterator getIncomingTransactions( + cash.z.wallet.sdk.rpc.Service.Empty request) { + return io.grpc.stub.ClientCalls.blockingServerStreamingCall( + getChannel(), getGetIncomingTransactionsMethod(), getCallOptions(), request); + } + + /** + *
+     * Clear the incoming transaction pool.
+     * 
+ */ + public cash.z.wallet.sdk.rpc.Service.Empty clearIncomingTransactions(cash.z.wallet.sdk.rpc.Service.Empty request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getClearIncomingTransactionsMethod(), getCallOptions(), request); + } + } + + /** + *
+   * Darksidewalletd maintains two staging areas, blocks and transactions. The
+   * Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything
+   * in the staging area to the working (operational) state that the mock zcashd
+   * serves; transactions are placed into their corresponding blocks (by height).
+   * 
+ */ + public static final class DarksideStreamerFutureStub extends io.grpc.stub.AbstractFutureStub { + private DarksideStreamerFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected DarksideStreamerFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new DarksideStreamerFutureStub(channel, callOptions); + } + + /** + *
+     * Reset reverts all darksidewalletd state (active block range, latest height,
+     * staged blocks and transactions) and lightwalletd state (cache) to empty,
+     * the same as the initial state. This occurs synchronously and instantaneously;
+     * no reorg happens in lightwalletd. This is good to do before each independent
+     * test so that no state leaks from one test to another.
+     * Also sets (some of) the values returned by GetLightdInfo(). The Sapling
+     * activation height specified here must be where the block range starts.
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture reset( + cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getResetMethod(), getCallOptions()), request); + } + + /** + *
+     * StageBlocks is the same as StageBlocksStream() except the blocks are fetched
+     * from the given URL. Blocks are one per line, hex-encoded (not JSON).
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture stageBlocks( + cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getStageBlocksMethod(), getCallOptions()), request); + } + + /** + *
+     * StageBlocksCreate is like the previous two, except it creates 'count'
+     * empty blocks at consecutive heights starting at height 'height'. The
+     * 'nonce' is part of the header, so it contributes to the block hash; this
+     * lets you create identical blocks (same transactions and height), but with
+     * different hashes.
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture stageBlocksCreate( + cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getStageBlocksCreateMethod(), getCallOptions()), request); + } + + /** + *
+     * StageTransactions is the same except the transactions are fetched from
+     * the given url. They are all staged into the block at the given height.
+     * Staging transactions to different heights requires multiple calls.
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture stageTransactions( + cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getStageTransactionsMethod(), getCallOptions()), request); + } + + /** + *
+     * ApplyStaged iterates the list of blocks that were staged by the
+     * StageBlocks*() gRPCs, in the order they were staged, and "merges" each
+     * into the active, working blocks list that the mock zcashd is presenting
+     * to lightwalletd. Even as each block is applied, the active list can't
+     * have gaps; if the active block range is 1000-1006, and the staged block
+     * range is 1003-1004, the resulting range is 1000-1004, with 1000-1002
+     * unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped.
+     * After merging all blocks, ApplyStaged() appends staged transactions (in
+     * the order received) into each one's corresponding (by height) block
+     * The staging area is then cleared.
+     * The argument specifies the latest block height that mock zcashd reports
+     * (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
+     * also be used to simply advance the latest block height presented by mock
+     * zcashd. That is, there doesn't need to be anything in the staging area.
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture applyStaged( + cash.z.wallet.sdk.rpc.Darkside.DarksideHeight request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getApplyStagedMethod(), getCallOptions()), request); + } + + /** + *
+     * Clear the incoming transaction pool.
+     * 
+ */ + public com.google.common.util.concurrent.ListenableFuture clearIncomingTransactions( + cash.z.wallet.sdk.rpc.Service.Empty request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getClearIncomingTransactionsMethod(), getCallOptions()), request); + } + } + + private static final int METHODID_RESET = 0; + private static final int METHODID_STAGE_BLOCKS = 1; + private static final int METHODID_STAGE_BLOCKS_CREATE = 2; + private static final int METHODID_STAGE_TRANSACTIONS = 3; + private static final int METHODID_APPLY_STAGED = 4; + private static final int METHODID_GET_INCOMING_TRANSACTIONS = 5; + private static final int METHODID_CLEAR_INCOMING_TRANSACTIONS = 6; + private static final int METHODID_STAGE_BLOCKS_STREAM = 7; + private static final int METHODID_STAGE_TRANSACTIONS_STREAM = 8; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final DarksideStreamerImplBase serviceImpl; + private final int methodId; + + MethodHandlers(DarksideStreamerImplBase serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_RESET: + serviceImpl.reset((cash.z.wallet.sdk.rpc.Darkside.DarksideMetaState) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_STAGE_BLOCKS: + serviceImpl.stageBlocks((cash.z.wallet.sdk.rpc.Darkside.DarksideBlocksURL) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_STAGE_BLOCKS_CREATE: + serviceImpl.stageBlocksCreate((cash.z.wallet.sdk.rpc.Darkside.DarksideEmptyBlocks) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_STAGE_TRANSACTIONS: + serviceImpl.stageTransactions((cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_APPLY_STAGED: + serviceImpl.applyStaged((cash.z.wallet.sdk.rpc.Darkside.DarksideHeight) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_GET_INCOMING_TRANSACTIONS: + serviceImpl.getIncomingTransactions((cash.z.wallet.sdk.rpc.Service.Empty) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_CLEAR_INCOMING_TRANSACTIONS: + serviceImpl.clearIncomingTransactions((cash.z.wallet.sdk.rpc.Service.Empty) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + default: + throw new AssertionError(); + } + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_STAGE_BLOCKS_STREAM: + return (io.grpc.stub.StreamObserver) serviceImpl.stageBlocksStream( + (io.grpc.stub.StreamObserver) responseObserver); + case METHODID_STAGE_TRANSACTIONS_STREAM: + return (io.grpc.stub.StreamObserver) serviceImpl.stageTransactionsStream( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + private static abstract class DarksideStreamerBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + DarksideStreamerBaseDescriptorSupplier() {} + + @java.lang.Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return cash.z.wallet.sdk.rpc.Darkside.getDescriptor(); + } + + @java.lang.Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("DarksideStreamer"); + } + } + + private static final class DarksideStreamerFileDescriptorSupplier + extends DarksideStreamerBaseDescriptorSupplier { + DarksideStreamerFileDescriptorSupplier() {} + } + + private static final class DarksideStreamerMethodDescriptorSupplier + extends DarksideStreamerBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final String methodName; + + DarksideStreamerMethodDescriptorSupplier(String methodName) { + this.methodName = methodName; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (DarksideStreamerGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new DarksideStreamerFileDescriptorSupplier()) + .addMethod(getResetMethod()) + .addMethod(getStageBlocksStreamMethod()) + .addMethod(getStageBlocksMethod()) + .addMethod(getStageBlocksCreateMethod()) + .addMethod(getStageTransactionsStreamMethod()) + .addMethod(getStageTransactionsMethod()) + .addMethod(getApplyStagedMethod()) + .addMethod(getGetIncomingTransactionsMethod()) + .addMethod(getClearIncomingTransactionsMethod()) + .build(); + } + } + } + return result; + } +} diff --git a/src/main/java/cash/z/wallet/sdk/rpc/Service.java b/src/main/java/cash/z/wallet/sdk/rpc/Service.java new file mode 100644 index 00000000..3ffc0247 --- /dev/null +++ b/src/main/java/cash/z/wallet/sdk/rpc/Service.java @@ -0,0 +1,15106 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: service.proto + +package cash.z.wallet.sdk.rpc; + +public final class Service { + private Service() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + public interface BlockIDOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.BlockID) + com.google.protobuf.MessageOrBuilder { + + /** + * uint64 height = 1; + * @return The height. + */ + long getHeight(); + + /** + * bytes hash = 2; + * @return The hash. + */ + com.google.protobuf.ByteString getHash(); + } + /** + *
+   * A BlockID message contains identifiers to select a block: a height or a
+   * hash. Specification by hash is not implemented, but may be in the future.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.BlockID} + */ + public static final class BlockID extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.BlockID) + BlockIDOrBuilder { + private static final long serialVersionUID = 0L; + // Use BlockID.newBuilder() to construct. + private BlockID(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private BlockID() { + hash_ = com.google.protobuf.ByteString.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new BlockID(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private BlockID( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + height_ = input.readUInt64(); + break; + } + case 18: { + + hash_ = input.readBytes(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockID_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockID_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.BlockID.class, cash.z.wallet.sdk.rpc.Service.BlockID.Builder.class); + } + + public static final int HEIGHT_FIELD_NUMBER = 1; + private long height_; + /** + * uint64 height = 1; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + + public static final int HASH_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString hash_; + /** + * bytes hash = 2; + * @return The hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHash() { + return hash_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (height_ != 0L) { + output.writeUInt64(1, height_); + } + if (!hash_.isEmpty()) { + output.writeBytes(2, hash_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (height_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(1, height_); + } + if (!hash_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, hash_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.BlockID)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.BlockID other = (cash.z.wallet.sdk.rpc.Service.BlockID) obj; + + if (getHeight() + != other.getHeight()) return false; + if (!getHash() + .equals(other.getHash())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + HEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getHeight()); + hash = (37 * hash) + HASH_FIELD_NUMBER; + hash = (53 * hash) + getHash().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.BlockID parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.BlockID prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * A BlockID message contains identifiers to select a block: a height or a
+     * hash. Specification by hash is not implemented, but may be in the future.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.BlockID} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.BlockID) + cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockID_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockID_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.BlockID.class, cash.z.wallet.sdk.rpc.Service.BlockID.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.BlockID.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + height_ = 0L; + + hash_ = com.google.protobuf.ByteString.EMPTY; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockID_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockID getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockID build() { + cash.z.wallet.sdk.rpc.Service.BlockID result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockID buildPartial() { + cash.z.wallet.sdk.rpc.Service.BlockID result = new cash.z.wallet.sdk.rpc.Service.BlockID(this); + result.height_ = height_; + result.hash_ = hash_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.BlockID) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.BlockID)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.BlockID other) { + if (other == cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance()) return this; + if (other.getHeight() != 0L) { + setHeight(other.getHeight()); + } + if (other.getHash() != com.google.protobuf.ByteString.EMPTY) { + setHash(other.getHash()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.BlockID parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.BlockID) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private long height_ ; + /** + * uint64 height = 1; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + /** + * uint64 height = 1; + * @param value The height to set. + * @return This builder for chaining. + */ + public Builder setHeight(long value) { + + height_ = value; + onChanged(); + return this; + } + /** + * uint64 height = 1; + * @return This builder for chaining. + */ + public Builder clearHeight() { + + height_ = 0L; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString hash_ = com.google.protobuf.ByteString.EMPTY; + /** + * bytes hash = 2; + * @return The hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHash() { + return hash_; + } + /** + * bytes hash = 2; + * @param value The hash to set. + * @return This builder for chaining. + */ + public Builder setHash(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + hash_ = value; + onChanged(); + return this; + } + /** + * bytes hash = 2; + * @return This builder for chaining. + */ + public Builder clearHash() { + + hash_ = getDefaultInstance().getHash(); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.BlockID) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.BlockID) + private static final cash.z.wallet.sdk.rpc.Service.BlockID DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.BlockID(); + } + + public static cash.z.wallet.sdk.rpc.Service.BlockID getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public BlockID parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new BlockID(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockID getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface BlockRangeOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.BlockRange) + com.google.protobuf.MessageOrBuilder { + + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + * @return Whether the start field is set. + */ + boolean hasStart(); + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + * @return The start. + */ + cash.z.wallet.sdk.rpc.Service.BlockID getStart(); + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getStartOrBuilder(); + + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + * @return Whether the end field is set. + */ + boolean hasEnd(); + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + * @return The end. + */ + cash.z.wallet.sdk.rpc.Service.BlockID getEnd(); + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getEndOrBuilder(); + } + /** + *
+   * BlockRange specifies a series of blocks from start to end inclusive.
+   * Both BlockIDs must be heights; specification by hash is not yet supported.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.BlockRange} + */ + public static final class BlockRange extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.BlockRange) + BlockRangeOrBuilder { + private static final long serialVersionUID = 0L; + // Use BlockRange.newBuilder() to construct. + private BlockRange(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private BlockRange() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new BlockRange(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private BlockRange( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + cash.z.wallet.sdk.rpc.Service.BlockID.Builder subBuilder = null; + if (start_ != null) { + subBuilder = start_.toBuilder(); + } + start_ = input.readMessage(cash.z.wallet.sdk.rpc.Service.BlockID.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(start_); + start_ = subBuilder.buildPartial(); + } + + break; + } + case 18: { + cash.z.wallet.sdk.rpc.Service.BlockID.Builder subBuilder = null; + if (end_ != null) { + subBuilder = end_.toBuilder(); + } + end_ = input.readMessage(cash.z.wallet.sdk.rpc.Service.BlockID.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(end_); + end_ = subBuilder.buildPartial(); + } + + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockRange_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockRange_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.BlockRange.class, cash.z.wallet.sdk.rpc.Service.BlockRange.Builder.class); + } + + public static final int START_FIELD_NUMBER = 1; + private cash.z.wallet.sdk.rpc.Service.BlockID start_; + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + * @return Whether the start field is set. + */ + @java.lang.Override + public boolean hasStart() { + return start_ != null; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + * @return The start. + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockID getStart() { + return start_ == null ? cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : start_; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getStartOrBuilder() { + return getStart(); + } + + public static final int END_FIELD_NUMBER = 2; + private cash.z.wallet.sdk.rpc.Service.BlockID end_; + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + * @return Whether the end field is set. + */ + @java.lang.Override + public boolean hasEnd() { + return end_ != null; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + * @return The end. + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockID getEnd() { + return end_ == null ? cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : end_; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getEndOrBuilder() { + return getEnd(); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (start_ != null) { + output.writeMessage(1, getStart()); + } + if (end_ != null) { + output.writeMessage(2, getEnd()); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (start_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, getStart()); + } + if (end_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, getEnd()); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.BlockRange)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.BlockRange other = (cash.z.wallet.sdk.rpc.Service.BlockRange) obj; + + if (hasStart() != other.hasStart()) return false; + if (hasStart()) { + if (!getStart() + .equals(other.getStart())) return false; + } + if (hasEnd() != other.hasEnd()) return false; + if (hasEnd()) { + if (!getEnd() + .equals(other.getEnd())) return false; + } + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasStart()) { + hash = (37 * hash) + START_FIELD_NUMBER; + hash = (53 * hash) + getStart().hashCode(); + } + if (hasEnd()) { + hash = (37 * hash) + END_FIELD_NUMBER; + hash = (53 * hash) + getEnd().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.BlockRange parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.BlockRange prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * BlockRange specifies a series of blocks from start to end inclusive.
+     * Both BlockIDs must be heights; specification by hash is not yet supported.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.BlockRange} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.BlockRange) + cash.z.wallet.sdk.rpc.Service.BlockRangeOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockRange_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockRange_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.BlockRange.class, cash.z.wallet.sdk.rpc.Service.BlockRange.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.BlockRange.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (startBuilder_ == null) { + start_ = null; + } else { + start_ = null; + startBuilder_ = null; + } + if (endBuilder_ == null) { + end_ = null; + } else { + end_ = null; + endBuilder_ = null; + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_BlockRange_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockRange getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.BlockRange.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockRange build() { + cash.z.wallet.sdk.rpc.Service.BlockRange result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockRange buildPartial() { + cash.z.wallet.sdk.rpc.Service.BlockRange result = new cash.z.wallet.sdk.rpc.Service.BlockRange(this); + if (startBuilder_ == null) { + result.start_ = start_; + } else { + result.start_ = startBuilder_.build(); + } + if (endBuilder_ == null) { + result.end_ = end_; + } else { + result.end_ = endBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.BlockRange) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.BlockRange)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.BlockRange other) { + if (other == cash.z.wallet.sdk.rpc.Service.BlockRange.getDefaultInstance()) return this; + if (other.hasStart()) { + mergeStart(other.getStart()); + } + if (other.hasEnd()) { + mergeEnd(other.getEnd()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.BlockRange parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.BlockRange) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private cash.z.wallet.sdk.rpc.Service.BlockID start_; + private com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder> startBuilder_; + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + * @return Whether the start field is set. + */ + public boolean hasStart() { + return startBuilder_ != null || start_ != null; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + * @return The start. + */ + public cash.z.wallet.sdk.rpc.Service.BlockID getStart() { + if (startBuilder_ == null) { + return start_ == null ? cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : start_; + } else { + return startBuilder_.getMessage(); + } + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + public Builder setStart(cash.z.wallet.sdk.rpc.Service.BlockID value) { + if (startBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + start_ = value; + onChanged(); + } else { + startBuilder_.setMessage(value); + } + + return this; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + public Builder setStart( + cash.z.wallet.sdk.rpc.Service.BlockID.Builder builderForValue) { + if (startBuilder_ == null) { + start_ = builderForValue.build(); + onChanged(); + } else { + startBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + public Builder mergeStart(cash.z.wallet.sdk.rpc.Service.BlockID value) { + if (startBuilder_ == null) { + if (start_ != null) { + start_ = + cash.z.wallet.sdk.rpc.Service.BlockID.newBuilder(start_).mergeFrom(value).buildPartial(); + } else { + start_ = value; + } + onChanged(); + } else { + startBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + public Builder clearStart() { + if (startBuilder_ == null) { + start_ = null; + onChanged(); + } else { + start_ = null; + startBuilder_ = null; + } + + return this; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + public cash.z.wallet.sdk.rpc.Service.BlockID.Builder getStartBuilder() { + + onChanged(); + return getStartFieldBuilder().getBuilder(); + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + public cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getStartOrBuilder() { + if (startBuilder_ != null) { + return startBuilder_.getMessageOrBuilder(); + } else { + return start_ == null ? + cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : start_; + } + } + /** + * .cash.z.wallet.sdk.rpc.BlockID start = 1; + */ + private com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder> + getStartFieldBuilder() { + if (startBuilder_ == null) { + startBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder>( + getStart(), + getParentForChildren(), + isClean()); + start_ = null; + } + return startBuilder_; + } + + private cash.z.wallet.sdk.rpc.Service.BlockID end_; + private com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder> endBuilder_; + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + * @return Whether the end field is set. + */ + public boolean hasEnd() { + return endBuilder_ != null || end_ != null; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + * @return The end. + */ + public cash.z.wallet.sdk.rpc.Service.BlockID getEnd() { + if (endBuilder_ == null) { + return end_ == null ? cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : end_; + } else { + return endBuilder_.getMessage(); + } + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + public Builder setEnd(cash.z.wallet.sdk.rpc.Service.BlockID value) { + if (endBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + end_ = value; + onChanged(); + } else { + endBuilder_.setMessage(value); + } + + return this; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + public Builder setEnd( + cash.z.wallet.sdk.rpc.Service.BlockID.Builder builderForValue) { + if (endBuilder_ == null) { + end_ = builderForValue.build(); + onChanged(); + } else { + endBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + public Builder mergeEnd(cash.z.wallet.sdk.rpc.Service.BlockID value) { + if (endBuilder_ == null) { + if (end_ != null) { + end_ = + cash.z.wallet.sdk.rpc.Service.BlockID.newBuilder(end_).mergeFrom(value).buildPartial(); + } else { + end_ = value; + } + onChanged(); + } else { + endBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + public Builder clearEnd() { + if (endBuilder_ == null) { + end_ = null; + onChanged(); + } else { + end_ = null; + endBuilder_ = null; + } + + return this; + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + public cash.z.wallet.sdk.rpc.Service.BlockID.Builder getEndBuilder() { + + onChanged(); + return getEndFieldBuilder().getBuilder(); + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + public cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getEndOrBuilder() { + if (endBuilder_ != null) { + return endBuilder_.getMessageOrBuilder(); + } else { + return end_ == null ? + cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : end_; + } + } + /** + * .cash.z.wallet.sdk.rpc.BlockID end = 2; + */ + private com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder> + getEndFieldBuilder() { + if (endBuilder_ == null) { + endBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder>( + getEnd(), + getParentForChildren(), + isClean()); + end_ = null; + } + return endBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.BlockRange) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.BlockRange) + private static final cash.z.wallet.sdk.rpc.Service.BlockRange DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.BlockRange(); + } + + public static cash.z.wallet.sdk.rpc.Service.BlockRange getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public BlockRange parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new BlockRange(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockRange getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface TxFilterOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.TxFilter) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * block identifier, height or hash
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + * @return Whether the block field is set. + */ + boolean hasBlock(); + /** + *
+     * block identifier, height or hash
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + * @return The block. + */ + cash.z.wallet.sdk.rpc.Service.BlockID getBlock(); + /** + *
+     * block identifier, height or hash
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getBlockOrBuilder(); + + /** + *
+     * index within the block
+     * 
+ * + * uint64 index = 2; + * @return The index. + */ + long getIndex(); + + /** + *
+     * transaction ID (hash, txid)
+     * 
+ * + * bytes hash = 3; + * @return The hash. + */ + com.google.protobuf.ByteString getHash(); + } + /** + *
+   * A TxFilter contains the information needed to identify a particular
+   * transaction: either a block and an index, or a direct transaction hash.
+   * Currently, only specification by hash is supported.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.TxFilter} + */ + public static final class TxFilter extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.TxFilter) + TxFilterOrBuilder { + private static final long serialVersionUID = 0L; + // Use TxFilter.newBuilder() to construct. + private TxFilter(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private TxFilter() { + hash_ = com.google.protobuf.ByteString.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new TxFilter(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private TxFilter( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + cash.z.wallet.sdk.rpc.Service.BlockID.Builder subBuilder = null; + if (block_ != null) { + subBuilder = block_.toBuilder(); + } + block_ = input.readMessage(cash.z.wallet.sdk.rpc.Service.BlockID.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(block_); + block_ = subBuilder.buildPartial(); + } + + break; + } + case 16: { + + index_ = input.readUInt64(); + break; + } + case 26: { + + hash_ = input.readBytes(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TxFilter_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TxFilter_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.TxFilter.class, cash.z.wallet.sdk.rpc.Service.TxFilter.Builder.class); + } + + public static final int BLOCK_FIELD_NUMBER = 1; + private cash.z.wallet.sdk.rpc.Service.BlockID block_; + /** + *
+     * block identifier, height or hash
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + * @return Whether the block field is set. + */ + @java.lang.Override + public boolean hasBlock() { + return block_ != null; + } + /** + *
+     * block identifier, height or hash
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + * @return The block. + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockID getBlock() { + return block_ == null ? cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : block_; + } + /** + *
+     * block identifier, height or hash
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getBlockOrBuilder() { + return getBlock(); + } + + public static final int INDEX_FIELD_NUMBER = 2; + private long index_; + /** + *
+     * index within the block
+     * 
+ * + * uint64 index = 2; + * @return The index. + */ + @java.lang.Override + public long getIndex() { + return index_; + } + + public static final int HASH_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString hash_; + /** + *
+     * transaction ID (hash, txid)
+     * 
+ * + * bytes hash = 3; + * @return The hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHash() { + return hash_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (block_ != null) { + output.writeMessage(1, getBlock()); + } + if (index_ != 0L) { + output.writeUInt64(2, index_); + } + if (!hash_.isEmpty()) { + output.writeBytes(3, hash_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (block_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, getBlock()); + } + if (index_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(2, index_); + } + if (!hash_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, hash_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.TxFilter)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.TxFilter other = (cash.z.wallet.sdk.rpc.Service.TxFilter) obj; + + if (hasBlock() != other.hasBlock()) return false; + if (hasBlock()) { + if (!getBlock() + .equals(other.getBlock())) return false; + } + if (getIndex() + != other.getIndex()) return false; + if (!getHash() + .equals(other.getHash())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasBlock()) { + hash = (37 * hash) + BLOCK_FIELD_NUMBER; + hash = (53 * hash) + getBlock().hashCode(); + } + hash = (37 * hash) + INDEX_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getIndex()); + hash = (37 * hash) + HASH_FIELD_NUMBER; + hash = (53 * hash) + getHash().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TxFilter parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.TxFilter prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * A TxFilter contains the information needed to identify a particular
+     * transaction: either a block and an index, or a direct transaction hash.
+     * Currently, only specification by hash is supported.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.TxFilter} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.TxFilter) + cash.z.wallet.sdk.rpc.Service.TxFilterOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TxFilter_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TxFilter_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.TxFilter.class, cash.z.wallet.sdk.rpc.Service.TxFilter.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.TxFilter.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (blockBuilder_ == null) { + block_ = null; + } else { + block_ = null; + blockBuilder_ = null; + } + index_ = 0L; + + hash_ = com.google.protobuf.ByteString.EMPTY; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TxFilter_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TxFilter getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.TxFilter.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TxFilter build() { + cash.z.wallet.sdk.rpc.Service.TxFilter result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TxFilter buildPartial() { + cash.z.wallet.sdk.rpc.Service.TxFilter result = new cash.z.wallet.sdk.rpc.Service.TxFilter(this); + if (blockBuilder_ == null) { + result.block_ = block_; + } else { + result.block_ = blockBuilder_.build(); + } + result.index_ = index_; + result.hash_ = hash_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.TxFilter) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.TxFilter)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.TxFilter other) { + if (other == cash.z.wallet.sdk.rpc.Service.TxFilter.getDefaultInstance()) return this; + if (other.hasBlock()) { + mergeBlock(other.getBlock()); + } + if (other.getIndex() != 0L) { + setIndex(other.getIndex()); + } + if (other.getHash() != com.google.protobuf.ByteString.EMPTY) { + setHash(other.getHash()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.TxFilter parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.TxFilter) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private cash.z.wallet.sdk.rpc.Service.BlockID block_; + private com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder> blockBuilder_; + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + * @return Whether the block field is set. + */ + public boolean hasBlock() { + return blockBuilder_ != null || block_ != null; + } + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + * @return The block. + */ + public cash.z.wallet.sdk.rpc.Service.BlockID getBlock() { + if (blockBuilder_ == null) { + return block_ == null ? cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : block_; + } else { + return blockBuilder_.getMessage(); + } + } + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + public Builder setBlock(cash.z.wallet.sdk.rpc.Service.BlockID value) { + if (blockBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + block_ = value; + onChanged(); + } else { + blockBuilder_.setMessage(value); + } + + return this; + } + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + public Builder setBlock( + cash.z.wallet.sdk.rpc.Service.BlockID.Builder builderForValue) { + if (blockBuilder_ == null) { + block_ = builderForValue.build(); + onChanged(); + } else { + blockBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + public Builder mergeBlock(cash.z.wallet.sdk.rpc.Service.BlockID value) { + if (blockBuilder_ == null) { + if (block_ != null) { + block_ = + cash.z.wallet.sdk.rpc.Service.BlockID.newBuilder(block_).mergeFrom(value).buildPartial(); + } else { + block_ = value; + } + onChanged(); + } else { + blockBuilder_.mergeFrom(value); + } + + return this; + } + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + public Builder clearBlock() { + if (blockBuilder_ == null) { + block_ = null; + onChanged(); + } else { + block_ = null; + blockBuilder_ = null; + } + + return this; + } + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + public cash.z.wallet.sdk.rpc.Service.BlockID.Builder getBlockBuilder() { + + onChanged(); + return getBlockFieldBuilder().getBuilder(); + } + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + public cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder getBlockOrBuilder() { + if (blockBuilder_ != null) { + return blockBuilder_.getMessageOrBuilder(); + } else { + return block_ == null ? + cash.z.wallet.sdk.rpc.Service.BlockID.getDefaultInstance() : block_; + } + } + /** + *
+       * block identifier, height or hash
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockID block = 1; + */ + private com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder> + getBlockFieldBuilder() { + if (blockBuilder_ == null) { + blockBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockID, cash.z.wallet.sdk.rpc.Service.BlockID.Builder, cash.z.wallet.sdk.rpc.Service.BlockIDOrBuilder>( + getBlock(), + getParentForChildren(), + isClean()); + block_ = null; + } + return blockBuilder_; + } + + private long index_ ; + /** + *
+       * index within the block
+       * 
+ * + * uint64 index = 2; + * @return The index. + */ + @java.lang.Override + public long getIndex() { + return index_; + } + /** + *
+       * index within the block
+       * 
+ * + * uint64 index = 2; + * @param value The index to set. + * @return This builder for chaining. + */ + public Builder setIndex(long value) { + + index_ = value; + onChanged(); + return this; + } + /** + *
+       * index within the block
+       * 
+ * + * uint64 index = 2; + * @return This builder for chaining. + */ + public Builder clearIndex() { + + index_ = 0L; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString hash_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * transaction ID (hash, txid)
+       * 
+ * + * bytes hash = 3; + * @return The hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString getHash() { + return hash_; + } + /** + *
+       * transaction ID (hash, txid)
+       * 
+ * + * bytes hash = 3; + * @param value The hash to set. + * @return This builder for chaining. + */ + public Builder setHash(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + hash_ = value; + onChanged(); + return this; + } + /** + *
+       * transaction ID (hash, txid)
+       * 
+ * + * bytes hash = 3; + * @return This builder for chaining. + */ + public Builder clearHash() { + + hash_ = getDefaultInstance().getHash(); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.TxFilter) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.TxFilter) + private static final cash.z.wallet.sdk.rpc.Service.TxFilter DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.TxFilter(); + } + + public static cash.z.wallet.sdk.rpc.Service.TxFilter getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public TxFilter parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new TxFilter(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TxFilter getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface RawTransactionOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.RawTransaction) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * exact data returned by Zcash 'getrawtransaction'
+     * 
+ * + * bytes data = 1; + * @return The data. + */ + com.google.protobuf.ByteString getData(); + + /** + *
+     * height that the transaction was mined (or -1)
+     * 
+ * + * uint64 height = 2; + * @return The height. + */ + long getHeight(); + } + /** + *
+   * RawTransaction contains the complete transaction data. It also optionally includes 
+   * the block height in which the transaction was included.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.RawTransaction} + */ + public static final class RawTransaction extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.RawTransaction) + RawTransactionOrBuilder { + private static final long serialVersionUID = 0L; + // Use RawTransaction.newBuilder() to construct. + private RawTransaction(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private RawTransaction() { + data_ = com.google.protobuf.ByteString.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new RawTransaction(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private RawTransaction( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + + data_ = input.readBytes(); + break; + } + case 16: { + + height_ = input.readUInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_RawTransaction_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_RawTransaction_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.RawTransaction.class, cash.z.wallet.sdk.rpc.Service.RawTransaction.Builder.class); + } + + public static final int DATA_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString data_; + /** + *
+     * exact data returned by Zcash 'getrawtransaction'
+     * 
+ * + * bytes data = 1; + * @return The data. + */ + @java.lang.Override + public com.google.protobuf.ByteString getData() { + return data_; + } + + public static final int HEIGHT_FIELD_NUMBER = 2; + private long height_; + /** + *
+     * height that the transaction was mined (or -1)
+     * 
+ * + * uint64 height = 2; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!data_.isEmpty()) { + output.writeBytes(1, data_); + } + if (height_ != 0L) { + output.writeUInt64(2, height_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!data_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, data_); + } + if (height_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(2, height_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.RawTransaction)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.RawTransaction other = (cash.z.wallet.sdk.rpc.Service.RawTransaction) obj; + + if (!getData() + .equals(other.getData())) return false; + if (getHeight() + != other.getHeight()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + DATA_FIELD_NUMBER; + hash = (53 * hash) + getData().hashCode(); + hash = (37 * hash) + HEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getHeight()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.RawTransaction parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.RawTransaction prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * RawTransaction contains the complete transaction data. It also optionally includes 
+     * the block height in which the transaction was included.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.RawTransaction} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.RawTransaction) + cash.z.wallet.sdk.rpc.Service.RawTransactionOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_RawTransaction_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_RawTransaction_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.RawTransaction.class, cash.z.wallet.sdk.rpc.Service.RawTransaction.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.RawTransaction.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + data_ = com.google.protobuf.ByteString.EMPTY; + + height_ = 0L; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_RawTransaction_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.RawTransaction getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.RawTransaction.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.RawTransaction build() { + cash.z.wallet.sdk.rpc.Service.RawTransaction result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.RawTransaction buildPartial() { + cash.z.wallet.sdk.rpc.Service.RawTransaction result = new cash.z.wallet.sdk.rpc.Service.RawTransaction(this); + result.data_ = data_; + result.height_ = height_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.RawTransaction) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.RawTransaction)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.RawTransaction other) { + if (other == cash.z.wallet.sdk.rpc.Service.RawTransaction.getDefaultInstance()) return this; + if (other.getData() != com.google.protobuf.ByteString.EMPTY) { + setData(other.getData()); + } + if (other.getHeight() != 0L) { + setHeight(other.getHeight()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.RawTransaction parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.RawTransaction) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private com.google.protobuf.ByteString data_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * exact data returned by Zcash 'getrawtransaction'
+       * 
+ * + * bytes data = 1; + * @return The data. + */ + @java.lang.Override + public com.google.protobuf.ByteString getData() { + return data_; + } + /** + *
+       * exact data returned by Zcash 'getrawtransaction'
+       * 
+ * + * bytes data = 1; + * @param value The data to set. + * @return This builder for chaining. + */ + public Builder setData(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + data_ = value; + onChanged(); + return this; + } + /** + *
+       * exact data returned by Zcash 'getrawtransaction'
+       * 
+ * + * bytes data = 1; + * @return This builder for chaining. + */ + public Builder clearData() { + + data_ = getDefaultInstance().getData(); + onChanged(); + return this; + } + + private long height_ ; + /** + *
+       * height that the transaction was mined (or -1)
+       * 
+ * + * uint64 height = 2; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + /** + *
+       * height that the transaction was mined (or -1)
+       * 
+ * + * uint64 height = 2; + * @param value The height to set. + * @return This builder for chaining. + */ + public Builder setHeight(long value) { + + height_ = value; + onChanged(); + return this; + } + /** + *
+       * height that the transaction was mined (or -1)
+       * 
+ * + * uint64 height = 2; + * @return This builder for chaining. + */ + public Builder clearHeight() { + + height_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.RawTransaction) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.RawTransaction) + private static final cash.z.wallet.sdk.rpc.Service.RawTransaction DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.RawTransaction(); + } + + public static cash.z.wallet.sdk.rpc.Service.RawTransaction getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public RawTransaction parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new RawTransaction(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.RawTransaction getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface SendResponseOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.SendResponse) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 errorCode = 1; + * @return The errorCode. + */ + int getErrorCode(); + + /** + * string errorMessage = 2; + * @return The errorMessage. + */ + java.lang.String getErrorMessage(); + /** + * string errorMessage = 2; + * @return The bytes for errorMessage. + */ + com.google.protobuf.ByteString + getErrorMessageBytes(); + } + /** + *
+   * A SendResponse encodes an error code and a string. It is currently used
+   * only by SendTransaction(). If error code is zero, the operation was
+   * successful; if non-zero, it and the message specify the failure.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.SendResponse} + */ + public static final class SendResponse extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.SendResponse) + SendResponseOrBuilder { + private static final long serialVersionUID = 0L; + // Use SendResponse.newBuilder() to construct. + private SendResponse(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private SendResponse() { + errorMessage_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new SendResponse(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private SendResponse( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + errorCode_ = input.readInt32(); + break; + } + case 18: { + java.lang.String s = input.readStringRequireUtf8(); + + errorMessage_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_SendResponse_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_SendResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.SendResponse.class, cash.z.wallet.sdk.rpc.Service.SendResponse.Builder.class); + } + + public static final int ERRORCODE_FIELD_NUMBER = 1; + private int errorCode_; + /** + * int32 errorCode = 1; + * @return The errorCode. + */ + @java.lang.Override + public int getErrorCode() { + return errorCode_; + } + + public static final int ERRORMESSAGE_FIELD_NUMBER = 2; + private volatile java.lang.Object errorMessage_; + /** + * string errorMessage = 2; + * @return The errorMessage. + */ + @java.lang.Override + public java.lang.String getErrorMessage() { + java.lang.Object ref = errorMessage_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + errorMessage_ = s; + return s; + } + } + /** + * string errorMessage = 2; + * @return The bytes for errorMessage. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getErrorMessageBytes() { + java.lang.Object ref = errorMessage_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + errorMessage_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (errorCode_ != 0) { + output.writeInt32(1, errorCode_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(errorMessage_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, errorMessage_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (errorCode_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, errorCode_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(errorMessage_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, errorMessage_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.SendResponse)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.SendResponse other = (cash.z.wallet.sdk.rpc.Service.SendResponse) obj; + + if (getErrorCode() + != other.getErrorCode()) return false; + if (!getErrorMessage() + .equals(other.getErrorMessage())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ERRORCODE_FIELD_NUMBER; + hash = (53 * hash) + getErrorCode(); + hash = (37 * hash) + ERRORMESSAGE_FIELD_NUMBER; + hash = (53 * hash) + getErrorMessage().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.SendResponse parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.SendResponse prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * A SendResponse encodes an error code and a string. It is currently used
+     * only by SendTransaction(). If error code is zero, the operation was
+     * successful; if non-zero, it and the message specify the failure.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.SendResponse} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.SendResponse) + cash.z.wallet.sdk.rpc.Service.SendResponseOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_SendResponse_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_SendResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.SendResponse.class, cash.z.wallet.sdk.rpc.Service.SendResponse.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.SendResponse.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + errorCode_ = 0; + + errorMessage_ = ""; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_SendResponse_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.SendResponse getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.SendResponse.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.SendResponse build() { + cash.z.wallet.sdk.rpc.Service.SendResponse result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.SendResponse buildPartial() { + cash.z.wallet.sdk.rpc.Service.SendResponse result = new cash.z.wallet.sdk.rpc.Service.SendResponse(this); + result.errorCode_ = errorCode_; + result.errorMessage_ = errorMessage_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.SendResponse) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.SendResponse)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.SendResponse other) { + if (other == cash.z.wallet.sdk.rpc.Service.SendResponse.getDefaultInstance()) return this; + if (other.getErrorCode() != 0) { + setErrorCode(other.getErrorCode()); + } + if (!other.getErrorMessage().isEmpty()) { + errorMessage_ = other.errorMessage_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.SendResponse parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.SendResponse) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int errorCode_ ; + /** + * int32 errorCode = 1; + * @return The errorCode. + */ + @java.lang.Override + public int getErrorCode() { + return errorCode_; + } + /** + * int32 errorCode = 1; + * @param value The errorCode to set. + * @return This builder for chaining. + */ + public Builder setErrorCode(int value) { + + errorCode_ = value; + onChanged(); + return this; + } + /** + * int32 errorCode = 1; + * @return This builder for chaining. + */ + public Builder clearErrorCode() { + + errorCode_ = 0; + onChanged(); + return this; + } + + private java.lang.Object errorMessage_ = ""; + /** + * string errorMessage = 2; + * @return The errorMessage. + */ + public java.lang.String getErrorMessage() { + java.lang.Object ref = errorMessage_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + errorMessage_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string errorMessage = 2; + * @return The bytes for errorMessage. + */ + public com.google.protobuf.ByteString + getErrorMessageBytes() { + java.lang.Object ref = errorMessage_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + errorMessage_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string errorMessage = 2; + * @param value The errorMessage to set. + * @return This builder for chaining. + */ + public Builder setErrorMessage( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + errorMessage_ = value; + onChanged(); + return this; + } + /** + * string errorMessage = 2; + * @return This builder for chaining. + */ + public Builder clearErrorMessage() { + + errorMessage_ = getDefaultInstance().getErrorMessage(); + onChanged(); + return this; + } + /** + * string errorMessage = 2; + * @param value The bytes for errorMessage to set. + * @return This builder for chaining. + */ + public Builder setErrorMessageBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + errorMessage_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.SendResponse) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.SendResponse) + private static final cash.z.wallet.sdk.rpc.Service.SendResponse DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.SendResponse(); + } + + public static cash.z.wallet.sdk.rpc.Service.SendResponse getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public SendResponse parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new SendResponse(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.SendResponse getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ChainSpecOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.ChainSpec) + com.google.protobuf.MessageOrBuilder { + } + /** + *
+   * Chainspec is a placeholder to allow specification of a particular chain fork.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.ChainSpec} + */ + public static final class ChainSpec extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.ChainSpec) + ChainSpecOrBuilder { + private static final long serialVersionUID = 0L; + // Use ChainSpec.newBuilder() to construct. + private ChainSpec(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ChainSpec() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ChainSpec(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ChainSpec( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_ChainSpec_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_ChainSpec_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.ChainSpec.class, cash.z.wallet.sdk.rpc.Service.ChainSpec.Builder.class); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.ChainSpec)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.ChainSpec other = (cash.z.wallet.sdk.rpc.Service.ChainSpec) obj; + + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.ChainSpec parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.ChainSpec prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * Chainspec is a placeholder to allow specification of a particular chain fork.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.ChainSpec} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.ChainSpec) + cash.z.wallet.sdk.rpc.Service.ChainSpecOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_ChainSpec_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_ChainSpec_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.ChainSpec.class, cash.z.wallet.sdk.rpc.Service.ChainSpec.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.ChainSpec.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_ChainSpec_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.ChainSpec getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.ChainSpec.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.ChainSpec build() { + cash.z.wallet.sdk.rpc.Service.ChainSpec result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.ChainSpec buildPartial() { + cash.z.wallet.sdk.rpc.Service.ChainSpec result = new cash.z.wallet.sdk.rpc.Service.ChainSpec(this); + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.ChainSpec) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.ChainSpec)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.ChainSpec other) { + if (other == cash.z.wallet.sdk.rpc.Service.ChainSpec.getDefaultInstance()) return this; + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.ChainSpec parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.ChainSpec) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.ChainSpec) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.ChainSpec) + private static final cash.z.wallet.sdk.rpc.Service.ChainSpec DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.ChainSpec(); + } + + public static cash.z.wallet.sdk.rpc.Service.ChainSpec getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ChainSpec parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ChainSpec(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.ChainSpec getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface EmptyOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.Empty) + com.google.protobuf.MessageOrBuilder { + } + /** + *
+   * Empty is for gRPCs that take no arguments, currently only GetLightdInfo.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.Empty} + */ + public static final class Empty extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.Empty) + EmptyOrBuilder { + private static final long serialVersionUID = 0L; + // Use Empty.newBuilder() to construct. + private Empty(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Empty() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Empty(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Empty( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Empty_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Empty_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Empty.class, cash.z.wallet.sdk.rpc.Service.Empty.Builder.class); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.Empty)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.Empty other = (cash.z.wallet.sdk.rpc.Service.Empty) obj; + + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Empty parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.Empty prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * Empty is for gRPCs that take no arguments, currently only GetLightdInfo.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.Empty} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.Empty) + cash.z.wallet.sdk.rpc.Service.EmptyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Empty_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Empty_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Empty.class, cash.z.wallet.sdk.rpc.Service.Empty.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.Empty.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Empty_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Empty getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Empty build() { + cash.z.wallet.sdk.rpc.Service.Empty result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Empty buildPartial() { + cash.z.wallet.sdk.rpc.Service.Empty result = new cash.z.wallet.sdk.rpc.Service.Empty(this); + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.Empty) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.Empty)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.Empty other) { + if (other == cash.z.wallet.sdk.rpc.Service.Empty.getDefaultInstance()) return this; + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.Empty parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.Empty) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.Empty) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.Empty) + private static final cash.z.wallet.sdk.rpc.Service.Empty DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.Empty(); + } + + public static cash.z.wallet.sdk.rpc.Service.Empty getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Empty parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Empty(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Empty getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface LightdInfoOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.LightdInfo) + com.google.protobuf.MessageOrBuilder { + + /** + * string version = 1; + * @return The version. + */ + java.lang.String getVersion(); + /** + * string version = 1; + * @return The bytes for version. + */ + com.google.protobuf.ByteString + getVersionBytes(); + + /** + * string vendor = 2; + * @return The vendor. + */ + java.lang.String getVendor(); + /** + * string vendor = 2; + * @return The bytes for vendor. + */ + com.google.protobuf.ByteString + getVendorBytes(); + + /** + *
+     * true
+     * 
+ * + * bool taddrSupport = 3; + * @return The taddrSupport. + */ + boolean getTaddrSupport(); + + /** + *
+     * either "main" or "test"
+     * 
+ * + * string chainName = 4; + * @return The chainName. + */ + java.lang.String getChainName(); + /** + *
+     * either "main" or "test"
+     * 
+ * + * string chainName = 4; + * @return The bytes for chainName. + */ + com.google.protobuf.ByteString + getChainNameBytes(); + + /** + *
+     * depends on mainnet or testnet
+     * 
+ * + * uint64 saplingActivationHeight = 5; + * @return The saplingActivationHeight. + */ + long getSaplingActivationHeight(); + + /** + *
+     * protocol identifier, see consensus/upgrades.cpp
+     * 
+ * + * string consensusBranchId = 6; + * @return The consensusBranchId. + */ + java.lang.String getConsensusBranchId(); + /** + *
+     * protocol identifier, see consensus/upgrades.cpp
+     * 
+ * + * string consensusBranchId = 6; + * @return The bytes for consensusBranchId. + */ + com.google.protobuf.ByteString + getConsensusBranchIdBytes(); + + /** + *
+     * latest block on the best chain
+     * 
+ * + * uint64 blockHeight = 7; + * @return The blockHeight. + */ + long getBlockHeight(); + + /** + * string gitCommit = 8; + * @return The gitCommit. + */ + java.lang.String getGitCommit(); + /** + * string gitCommit = 8; + * @return The bytes for gitCommit. + */ + com.google.protobuf.ByteString + getGitCommitBytes(); + + /** + * string branch = 9; + * @return The branch. + */ + java.lang.String getBranch(); + /** + * string branch = 9; + * @return The bytes for branch. + */ + com.google.protobuf.ByteString + getBranchBytes(); + + /** + * string buildDate = 10; + * @return The buildDate. + */ + java.lang.String getBuildDate(); + /** + * string buildDate = 10; + * @return The bytes for buildDate. + */ + com.google.protobuf.ByteString + getBuildDateBytes(); + + /** + * string buildUser = 11; + * @return The buildUser. + */ + java.lang.String getBuildUser(); + /** + * string buildUser = 11; + * @return The bytes for buildUser. + */ + com.google.protobuf.ByteString + getBuildUserBytes(); + + /** + *
+     * less than tip height if pirated is syncing
+     * 
+ * + * uint64 estimatedHeight = 12; + * @return The estimatedHeight. + */ + long getEstimatedHeight(); + + /** + *
+     * example: "v4.1.1-877212414"
+     * 
+ * + * string piratedBuild = 13; + * @return The piratedBuild. + */ + java.lang.String getPiratedBuild(); + /** + *
+     * example: "v4.1.1-877212414"
+     * 
+ * + * string piratedBuild = 13; + * @return The bytes for piratedBuild. + */ + com.google.protobuf.ByteString + getPiratedBuildBytes(); + + /** + *
+     * example: "/MagicBean:4.1.1/"
+     * 
+ * + * string piratedSubversion = 14; + * @return The piratedSubversion. + */ + java.lang.String getPiratedSubversion(); + /** + *
+     * example: "/MagicBean:4.1.1/"
+     * 
+ * + * string piratedSubversion = 14; + * @return The bytes for piratedSubversion. + */ + com.google.protobuf.ByteString + getPiratedSubversionBytes(); + } + /** + *
+   * LightdInfo returns various information about this lightwalletd instance
+   * and the state of the blockchain.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.LightdInfo} + */ + public static final class LightdInfo extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.LightdInfo) + LightdInfoOrBuilder { + private static final long serialVersionUID = 0L; + // Use LightdInfo.newBuilder() to construct. + private LightdInfo(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private LightdInfo() { + version_ = ""; + vendor_ = ""; + chainName_ = ""; + consensusBranchId_ = ""; + gitCommit_ = ""; + branch_ = ""; + buildDate_ = ""; + buildUser_ = ""; + piratedBuild_ = ""; + piratedSubversion_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new LightdInfo(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private LightdInfo( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + java.lang.String s = input.readStringRequireUtf8(); + + version_ = s; + break; + } + case 18: { + java.lang.String s = input.readStringRequireUtf8(); + + vendor_ = s; + break; + } + case 24: { + + taddrSupport_ = input.readBool(); + break; + } + case 34: { + java.lang.String s = input.readStringRequireUtf8(); + + chainName_ = s; + break; + } + case 40: { + + saplingActivationHeight_ = input.readUInt64(); + break; + } + case 50: { + java.lang.String s = input.readStringRequireUtf8(); + + consensusBranchId_ = s; + break; + } + case 56: { + + blockHeight_ = input.readUInt64(); + break; + } + case 66: { + java.lang.String s = input.readStringRequireUtf8(); + + gitCommit_ = s; + break; + } + case 74: { + java.lang.String s = input.readStringRequireUtf8(); + + branch_ = s; + break; + } + case 82: { + java.lang.String s = input.readStringRequireUtf8(); + + buildDate_ = s; + break; + } + case 90: { + java.lang.String s = input.readStringRequireUtf8(); + + buildUser_ = s; + break; + } + case 96: { + + estimatedHeight_ = input.readUInt64(); + break; + } + case 106: { + java.lang.String s = input.readStringRequireUtf8(); + + piratedBuild_ = s; + break; + } + case 114: { + java.lang.String s = input.readStringRequireUtf8(); + + piratedSubversion_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_LightdInfo_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_LightdInfo_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.LightdInfo.class, cash.z.wallet.sdk.rpc.Service.LightdInfo.Builder.class); + } + + public static final int VERSION_FIELD_NUMBER = 1; + private volatile java.lang.Object version_; + /** + * string version = 1; + * @return The version. + */ + @java.lang.Override + public java.lang.String getVersion() { + java.lang.Object ref = version_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + version_ = s; + return s; + } + } + /** + * string version = 1; + * @return The bytes for version. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getVersionBytes() { + java.lang.Object ref = version_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + version_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int VENDOR_FIELD_NUMBER = 2; + private volatile java.lang.Object vendor_; + /** + * string vendor = 2; + * @return The vendor. + */ + @java.lang.Override + public java.lang.String getVendor() { + java.lang.Object ref = vendor_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + vendor_ = s; + return s; + } + } + /** + * string vendor = 2; + * @return The bytes for vendor. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getVendorBytes() { + java.lang.Object ref = vendor_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + vendor_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int TADDRSUPPORT_FIELD_NUMBER = 3; + private boolean taddrSupport_; + /** + *
+     * true
+     * 
+ * + * bool taddrSupport = 3; + * @return The taddrSupport. + */ + @java.lang.Override + public boolean getTaddrSupport() { + return taddrSupport_; + } + + public static final int CHAINNAME_FIELD_NUMBER = 4; + private volatile java.lang.Object chainName_; + /** + *
+     * either "main" or "test"
+     * 
+ * + * string chainName = 4; + * @return The chainName. + */ + @java.lang.Override + public java.lang.String getChainName() { + java.lang.Object ref = chainName_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + chainName_ = s; + return s; + } + } + /** + *
+     * either "main" or "test"
+     * 
+ * + * string chainName = 4; + * @return The bytes for chainName. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getChainNameBytes() { + java.lang.Object ref = chainName_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + chainName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int SAPLINGACTIVATIONHEIGHT_FIELD_NUMBER = 5; + private long saplingActivationHeight_; + /** + *
+     * depends on mainnet or testnet
+     * 
+ * + * uint64 saplingActivationHeight = 5; + * @return The saplingActivationHeight. + */ + @java.lang.Override + public long getSaplingActivationHeight() { + return saplingActivationHeight_; + } + + public static final int CONSENSUSBRANCHID_FIELD_NUMBER = 6; + private volatile java.lang.Object consensusBranchId_; + /** + *
+     * protocol identifier, see consensus/upgrades.cpp
+     * 
+ * + * string consensusBranchId = 6; + * @return The consensusBranchId. + */ + @java.lang.Override + public java.lang.String getConsensusBranchId() { + java.lang.Object ref = consensusBranchId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + consensusBranchId_ = s; + return s; + } + } + /** + *
+     * protocol identifier, see consensus/upgrades.cpp
+     * 
+ * + * string consensusBranchId = 6; + * @return The bytes for consensusBranchId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getConsensusBranchIdBytes() { + java.lang.Object ref = consensusBranchId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + consensusBranchId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int BLOCKHEIGHT_FIELD_NUMBER = 7; + private long blockHeight_; + /** + *
+     * latest block on the best chain
+     * 
+ * + * uint64 blockHeight = 7; + * @return The blockHeight. + */ + @java.lang.Override + public long getBlockHeight() { + return blockHeight_; + } + + public static final int GITCOMMIT_FIELD_NUMBER = 8; + private volatile java.lang.Object gitCommit_; + /** + * string gitCommit = 8; + * @return The gitCommit. + */ + @java.lang.Override + public java.lang.String getGitCommit() { + java.lang.Object ref = gitCommit_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + gitCommit_ = s; + return s; + } + } + /** + * string gitCommit = 8; + * @return The bytes for gitCommit. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getGitCommitBytes() { + java.lang.Object ref = gitCommit_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + gitCommit_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int BRANCH_FIELD_NUMBER = 9; + private volatile java.lang.Object branch_; + /** + * string branch = 9; + * @return The branch. + */ + @java.lang.Override + public java.lang.String getBranch() { + java.lang.Object ref = branch_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + branch_ = s; + return s; + } + } + /** + * string branch = 9; + * @return The bytes for branch. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getBranchBytes() { + java.lang.Object ref = branch_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + branch_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int BUILDDATE_FIELD_NUMBER = 10; + private volatile java.lang.Object buildDate_; + /** + * string buildDate = 10; + * @return The buildDate. + */ + @java.lang.Override + public java.lang.String getBuildDate() { + java.lang.Object ref = buildDate_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + buildDate_ = s; + return s; + } + } + /** + * string buildDate = 10; + * @return The bytes for buildDate. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getBuildDateBytes() { + java.lang.Object ref = buildDate_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + buildDate_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int BUILDUSER_FIELD_NUMBER = 11; + private volatile java.lang.Object buildUser_; + /** + * string buildUser = 11; + * @return The buildUser. + */ + @java.lang.Override + public java.lang.String getBuildUser() { + java.lang.Object ref = buildUser_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + buildUser_ = s; + return s; + } + } + /** + * string buildUser = 11; + * @return The bytes for buildUser. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getBuildUserBytes() { + java.lang.Object ref = buildUser_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + buildUser_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ESTIMATEDHEIGHT_FIELD_NUMBER = 12; + private long estimatedHeight_; + /** + *
+     * less than tip height if pirated is syncing
+     * 
+ * + * uint64 estimatedHeight = 12; + * @return The estimatedHeight. + */ + @java.lang.Override + public long getEstimatedHeight() { + return estimatedHeight_; + } + + public static final int PIRATEDBUILD_FIELD_NUMBER = 13; + private volatile java.lang.Object piratedBuild_; + /** + *
+     * example: "v4.1.1-877212414"
+     * 
+ * + * string piratedBuild = 13; + * @return The piratedBuild. + */ + @java.lang.Override + public java.lang.String getPiratedBuild() { + java.lang.Object ref = piratedBuild_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + piratedBuild_ = s; + return s; + } + } + /** + *
+     * example: "v4.1.1-877212414"
+     * 
+ * + * string piratedBuild = 13; + * @return The bytes for piratedBuild. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getPiratedBuildBytes() { + java.lang.Object ref = piratedBuild_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + piratedBuild_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PIRATEDSUBVERSION_FIELD_NUMBER = 14; + private volatile java.lang.Object piratedSubversion_; + /** + *
+     * example: "/MagicBean:4.1.1/"
+     * 
+ * + * string piratedSubversion = 14; + * @return The piratedSubversion. + */ + @java.lang.Override + public java.lang.String getPiratedSubversion() { + java.lang.Object ref = piratedSubversion_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + piratedSubversion_ = s; + return s; + } + } + /** + *
+     * example: "/MagicBean:4.1.1/"
+     * 
+ * + * string piratedSubversion = 14; + * @return The bytes for piratedSubversion. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getPiratedSubversionBytes() { + java.lang.Object ref = piratedSubversion_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + piratedSubversion_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(version_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, version_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(vendor_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, vendor_); + } + if (taddrSupport_ != false) { + output.writeBool(3, taddrSupport_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(chainName_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 4, chainName_); + } + if (saplingActivationHeight_ != 0L) { + output.writeUInt64(5, saplingActivationHeight_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(consensusBranchId_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 6, consensusBranchId_); + } + if (blockHeight_ != 0L) { + output.writeUInt64(7, blockHeight_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(gitCommit_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 8, gitCommit_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(branch_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 9, branch_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(buildDate_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 10, buildDate_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(buildUser_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 11, buildUser_); + } + if (estimatedHeight_ != 0L) { + output.writeUInt64(12, estimatedHeight_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(piratedBuild_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 13, piratedBuild_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(piratedSubversion_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 14, piratedSubversion_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(version_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, version_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(vendor_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, vendor_); + } + if (taddrSupport_ != false) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(3, taddrSupport_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(chainName_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, chainName_); + } + if (saplingActivationHeight_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(5, saplingActivationHeight_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(consensusBranchId_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(6, consensusBranchId_); + } + if (blockHeight_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(7, blockHeight_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(gitCommit_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, gitCommit_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(branch_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(9, branch_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(buildDate_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(10, buildDate_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(buildUser_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(11, buildUser_); + } + if (estimatedHeight_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(12, estimatedHeight_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(piratedBuild_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(13, piratedBuild_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(piratedSubversion_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(14, piratedSubversion_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.LightdInfo)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.LightdInfo other = (cash.z.wallet.sdk.rpc.Service.LightdInfo) obj; + + if (!getVersion() + .equals(other.getVersion())) return false; + if (!getVendor() + .equals(other.getVendor())) return false; + if (getTaddrSupport() + != other.getTaddrSupport()) return false; + if (!getChainName() + .equals(other.getChainName())) return false; + if (getSaplingActivationHeight() + != other.getSaplingActivationHeight()) return false; + if (!getConsensusBranchId() + .equals(other.getConsensusBranchId())) return false; + if (getBlockHeight() + != other.getBlockHeight()) return false; + if (!getGitCommit() + .equals(other.getGitCommit())) return false; + if (!getBranch() + .equals(other.getBranch())) return false; + if (!getBuildDate() + .equals(other.getBuildDate())) return false; + if (!getBuildUser() + .equals(other.getBuildUser())) return false; + if (getEstimatedHeight() + != other.getEstimatedHeight()) return false; + if (!getPiratedBuild() + .equals(other.getPiratedBuild())) return false; + if (!getPiratedSubversion() + .equals(other.getPiratedSubversion())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + VERSION_FIELD_NUMBER; + hash = (53 * hash) + getVersion().hashCode(); + hash = (37 * hash) + VENDOR_FIELD_NUMBER; + hash = (53 * hash) + getVendor().hashCode(); + hash = (37 * hash) + TADDRSUPPORT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getTaddrSupport()); + hash = (37 * hash) + CHAINNAME_FIELD_NUMBER; + hash = (53 * hash) + getChainName().hashCode(); + hash = (37 * hash) + SAPLINGACTIVATIONHEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getSaplingActivationHeight()); + hash = (37 * hash) + CONSENSUSBRANCHID_FIELD_NUMBER; + hash = (53 * hash) + getConsensusBranchId().hashCode(); + hash = (37 * hash) + BLOCKHEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getBlockHeight()); + hash = (37 * hash) + GITCOMMIT_FIELD_NUMBER; + hash = (53 * hash) + getGitCommit().hashCode(); + hash = (37 * hash) + BRANCH_FIELD_NUMBER; + hash = (53 * hash) + getBranch().hashCode(); + hash = (37 * hash) + BUILDDATE_FIELD_NUMBER; + hash = (53 * hash) + getBuildDate().hashCode(); + hash = (37 * hash) + BUILDUSER_FIELD_NUMBER; + hash = (53 * hash) + getBuildUser().hashCode(); + hash = (37 * hash) + ESTIMATEDHEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getEstimatedHeight()); + hash = (37 * hash) + PIRATEDBUILD_FIELD_NUMBER; + hash = (53 * hash) + getPiratedBuild().hashCode(); + hash = (37 * hash) + PIRATEDSUBVERSION_FIELD_NUMBER; + hash = (53 * hash) + getPiratedSubversion().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.LightdInfo parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.LightdInfo prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * LightdInfo returns various information about this lightwalletd instance
+     * and the state of the blockchain.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.LightdInfo} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.LightdInfo) + cash.z.wallet.sdk.rpc.Service.LightdInfoOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_LightdInfo_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_LightdInfo_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.LightdInfo.class, cash.z.wallet.sdk.rpc.Service.LightdInfo.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.LightdInfo.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + version_ = ""; + + vendor_ = ""; + + taddrSupport_ = false; + + chainName_ = ""; + + saplingActivationHeight_ = 0L; + + consensusBranchId_ = ""; + + blockHeight_ = 0L; + + gitCommit_ = ""; + + branch_ = ""; + + buildDate_ = ""; + + buildUser_ = ""; + + estimatedHeight_ = 0L; + + piratedBuild_ = ""; + + piratedSubversion_ = ""; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_LightdInfo_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.LightdInfo getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.LightdInfo.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.LightdInfo build() { + cash.z.wallet.sdk.rpc.Service.LightdInfo result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.LightdInfo buildPartial() { + cash.z.wallet.sdk.rpc.Service.LightdInfo result = new cash.z.wallet.sdk.rpc.Service.LightdInfo(this); + result.version_ = version_; + result.vendor_ = vendor_; + result.taddrSupport_ = taddrSupport_; + result.chainName_ = chainName_; + result.saplingActivationHeight_ = saplingActivationHeight_; + result.consensusBranchId_ = consensusBranchId_; + result.blockHeight_ = blockHeight_; + result.gitCommit_ = gitCommit_; + result.branch_ = branch_; + result.buildDate_ = buildDate_; + result.buildUser_ = buildUser_; + result.estimatedHeight_ = estimatedHeight_; + result.piratedBuild_ = piratedBuild_; + result.piratedSubversion_ = piratedSubversion_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.LightdInfo) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.LightdInfo)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.LightdInfo other) { + if (other == cash.z.wallet.sdk.rpc.Service.LightdInfo.getDefaultInstance()) return this; + if (!other.getVersion().isEmpty()) { + version_ = other.version_; + onChanged(); + } + if (!other.getVendor().isEmpty()) { + vendor_ = other.vendor_; + onChanged(); + } + if (other.getTaddrSupport() != false) { + setTaddrSupport(other.getTaddrSupport()); + } + if (!other.getChainName().isEmpty()) { + chainName_ = other.chainName_; + onChanged(); + } + if (other.getSaplingActivationHeight() != 0L) { + setSaplingActivationHeight(other.getSaplingActivationHeight()); + } + if (!other.getConsensusBranchId().isEmpty()) { + consensusBranchId_ = other.consensusBranchId_; + onChanged(); + } + if (other.getBlockHeight() != 0L) { + setBlockHeight(other.getBlockHeight()); + } + if (!other.getGitCommit().isEmpty()) { + gitCommit_ = other.gitCommit_; + onChanged(); + } + if (!other.getBranch().isEmpty()) { + branch_ = other.branch_; + onChanged(); + } + if (!other.getBuildDate().isEmpty()) { + buildDate_ = other.buildDate_; + onChanged(); + } + if (!other.getBuildUser().isEmpty()) { + buildUser_ = other.buildUser_; + onChanged(); + } + if (other.getEstimatedHeight() != 0L) { + setEstimatedHeight(other.getEstimatedHeight()); + } + if (!other.getPiratedBuild().isEmpty()) { + piratedBuild_ = other.piratedBuild_; + onChanged(); + } + if (!other.getPiratedSubversion().isEmpty()) { + piratedSubversion_ = other.piratedSubversion_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.LightdInfo parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.LightdInfo) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private java.lang.Object version_ = ""; + /** + * string version = 1; + * @return The version. + */ + public java.lang.String getVersion() { + java.lang.Object ref = version_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + version_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string version = 1; + * @return The bytes for version. + */ + public com.google.protobuf.ByteString + getVersionBytes() { + java.lang.Object ref = version_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + version_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string version = 1; + * @param value The version to set. + * @return This builder for chaining. + */ + public Builder setVersion( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + version_ = value; + onChanged(); + return this; + } + /** + * string version = 1; + * @return This builder for chaining. + */ + public Builder clearVersion() { + + version_ = getDefaultInstance().getVersion(); + onChanged(); + return this; + } + /** + * string version = 1; + * @param value The bytes for version to set. + * @return This builder for chaining. + */ + public Builder setVersionBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + version_ = value; + onChanged(); + return this; + } + + private java.lang.Object vendor_ = ""; + /** + * string vendor = 2; + * @return The vendor. + */ + public java.lang.String getVendor() { + java.lang.Object ref = vendor_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + vendor_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string vendor = 2; + * @return The bytes for vendor. + */ + public com.google.protobuf.ByteString + getVendorBytes() { + java.lang.Object ref = vendor_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + vendor_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string vendor = 2; + * @param value The vendor to set. + * @return This builder for chaining. + */ + public Builder setVendor( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + vendor_ = value; + onChanged(); + return this; + } + /** + * string vendor = 2; + * @return This builder for chaining. + */ + public Builder clearVendor() { + + vendor_ = getDefaultInstance().getVendor(); + onChanged(); + return this; + } + /** + * string vendor = 2; + * @param value The bytes for vendor to set. + * @return This builder for chaining. + */ + public Builder setVendorBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + vendor_ = value; + onChanged(); + return this; + } + + private boolean taddrSupport_ ; + /** + *
+       * true
+       * 
+ * + * bool taddrSupport = 3; + * @return The taddrSupport. + */ + @java.lang.Override + public boolean getTaddrSupport() { + return taddrSupport_; + } + /** + *
+       * true
+       * 
+ * + * bool taddrSupport = 3; + * @param value The taddrSupport to set. + * @return This builder for chaining. + */ + public Builder setTaddrSupport(boolean value) { + + taddrSupport_ = value; + onChanged(); + return this; + } + /** + *
+       * true
+       * 
+ * + * bool taddrSupport = 3; + * @return This builder for chaining. + */ + public Builder clearTaddrSupport() { + + taddrSupport_ = false; + onChanged(); + return this; + } + + private java.lang.Object chainName_ = ""; + /** + *
+       * either "main" or "test"
+       * 
+ * + * string chainName = 4; + * @return The chainName. + */ + public java.lang.String getChainName() { + java.lang.Object ref = chainName_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + chainName_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * either "main" or "test"
+       * 
+ * + * string chainName = 4; + * @return The bytes for chainName. + */ + public com.google.protobuf.ByteString + getChainNameBytes() { + java.lang.Object ref = chainName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + chainName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * either "main" or "test"
+       * 
+ * + * string chainName = 4; + * @param value The chainName to set. + * @return This builder for chaining. + */ + public Builder setChainName( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + chainName_ = value; + onChanged(); + return this; + } + /** + *
+       * either "main" or "test"
+       * 
+ * + * string chainName = 4; + * @return This builder for chaining. + */ + public Builder clearChainName() { + + chainName_ = getDefaultInstance().getChainName(); + onChanged(); + return this; + } + /** + *
+       * either "main" or "test"
+       * 
+ * + * string chainName = 4; + * @param value The bytes for chainName to set. + * @return This builder for chaining. + */ + public Builder setChainNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + chainName_ = value; + onChanged(); + return this; + } + + private long saplingActivationHeight_ ; + /** + *
+       * depends on mainnet or testnet
+       * 
+ * + * uint64 saplingActivationHeight = 5; + * @return The saplingActivationHeight. + */ + @java.lang.Override + public long getSaplingActivationHeight() { + return saplingActivationHeight_; + } + /** + *
+       * depends on mainnet or testnet
+       * 
+ * + * uint64 saplingActivationHeight = 5; + * @param value The saplingActivationHeight to set. + * @return This builder for chaining. + */ + public Builder setSaplingActivationHeight(long value) { + + saplingActivationHeight_ = value; + onChanged(); + return this; + } + /** + *
+       * depends on mainnet or testnet
+       * 
+ * + * uint64 saplingActivationHeight = 5; + * @return This builder for chaining. + */ + public Builder clearSaplingActivationHeight() { + + saplingActivationHeight_ = 0L; + onChanged(); + return this; + } + + private java.lang.Object consensusBranchId_ = ""; + /** + *
+       * protocol identifier, see consensus/upgrades.cpp
+       * 
+ * + * string consensusBranchId = 6; + * @return The consensusBranchId. + */ + public java.lang.String getConsensusBranchId() { + java.lang.Object ref = consensusBranchId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + consensusBranchId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * protocol identifier, see consensus/upgrades.cpp
+       * 
+ * + * string consensusBranchId = 6; + * @return The bytes for consensusBranchId. + */ + public com.google.protobuf.ByteString + getConsensusBranchIdBytes() { + java.lang.Object ref = consensusBranchId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + consensusBranchId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * protocol identifier, see consensus/upgrades.cpp
+       * 
+ * + * string consensusBranchId = 6; + * @param value The consensusBranchId to set. + * @return This builder for chaining. + */ + public Builder setConsensusBranchId( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + consensusBranchId_ = value; + onChanged(); + return this; + } + /** + *
+       * protocol identifier, see consensus/upgrades.cpp
+       * 
+ * + * string consensusBranchId = 6; + * @return This builder for chaining. + */ + public Builder clearConsensusBranchId() { + + consensusBranchId_ = getDefaultInstance().getConsensusBranchId(); + onChanged(); + return this; + } + /** + *
+       * protocol identifier, see consensus/upgrades.cpp
+       * 
+ * + * string consensusBranchId = 6; + * @param value The bytes for consensusBranchId to set. + * @return This builder for chaining. + */ + public Builder setConsensusBranchIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + consensusBranchId_ = value; + onChanged(); + return this; + } + + private long blockHeight_ ; + /** + *
+       * latest block on the best chain
+       * 
+ * + * uint64 blockHeight = 7; + * @return The blockHeight. + */ + @java.lang.Override + public long getBlockHeight() { + return blockHeight_; + } + /** + *
+       * latest block on the best chain
+       * 
+ * + * uint64 blockHeight = 7; + * @param value The blockHeight to set. + * @return This builder for chaining. + */ + public Builder setBlockHeight(long value) { + + blockHeight_ = value; + onChanged(); + return this; + } + /** + *
+       * latest block on the best chain
+       * 
+ * + * uint64 blockHeight = 7; + * @return This builder for chaining. + */ + public Builder clearBlockHeight() { + + blockHeight_ = 0L; + onChanged(); + return this; + } + + private java.lang.Object gitCommit_ = ""; + /** + * string gitCommit = 8; + * @return The gitCommit. + */ + public java.lang.String getGitCommit() { + java.lang.Object ref = gitCommit_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + gitCommit_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string gitCommit = 8; + * @return The bytes for gitCommit. + */ + public com.google.protobuf.ByteString + getGitCommitBytes() { + java.lang.Object ref = gitCommit_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + gitCommit_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string gitCommit = 8; + * @param value The gitCommit to set. + * @return This builder for chaining. + */ + public Builder setGitCommit( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + gitCommit_ = value; + onChanged(); + return this; + } + /** + * string gitCommit = 8; + * @return This builder for chaining. + */ + public Builder clearGitCommit() { + + gitCommit_ = getDefaultInstance().getGitCommit(); + onChanged(); + return this; + } + /** + * string gitCommit = 8; + * @param value The bytes for gitCommit to set. + * @return This builder for chaining. + */ + public Builder setGitCommitBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + gitCommit_ = value; + onChanged(); + return this; + } + + private java.lang.Object branch_ = ""; + /** + * string branch = 9; + * @return The branch. + */ + public java.lang.String getBranch() { + java.lang.Object ref = branch_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + branch_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string branch = 9; + * @return The bytes for branch. + */ + public com.google.protobuf.ByteString + getBranchBytes() { + java.lang.Object ref = branch_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + branch_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string branch = 9; + * @param value The branch to set. + * @return This builder for chaining. + */ + public Builder setBranch( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + branch_ = value; + onChanged(); + return this; + } + /** + * string branch = 9; + * @return This builder for chaining. + */ + public Builder clearBranch() { + + branch_ = getDefaultInstance().getBranch(); + onChanged(); + return this; + } + /** + * string branch = 9; + * @param value The bytes for branch to set. + * @return This builder for chaining. + */ + public Builder setBranchBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + branch_ = value; + onChanged(); + return this; + } + + private java.lang.Object buildDate_ = ""; + /** + * string buildDate = 10; + * @return The buildDate. + */ + public java.lang.String getBuildDate() { + java.lang.Object ref = buildDate_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + buildDate_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string buildDate = 10; + * @return The bytes for buildDate. + */ + public com.google.protobuf.ByteString + getBuildDateBytes() { + java.lang.Object ref = buildDate_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + buildDate_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string buildDate = 10; + * @param value The buildDate to set. + * @return This builder for chaining. + */ + public Builder setBuildDate( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + buildDate_ = value; + onChanged(); + return this; + } + /** + * string buildDate = 10; + * @return This builder for chaining. + */ + public Builder clearBuildDate() { + + buildDate_ = getDefaultInstance().getBuildDate(); + onChanged(); + return this; + } + /** + * string buildDate = 10; + * @param value The bytes for buildDate to set. + * @return This builder for chaining. + */ + public Builder setBuildDateBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + buildDate_ = value; + onChanged(); + return this; + } + + private java.lang.Object buildUser_ = ""; + /** + * string buildUser = 11; + * @return The buildUser. + */ + public java.lang.String getBuildUser() { + java.lang.Object ref = buildUser_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + buildUser_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string buildUser = 11; + * @return The bytes for buildUser. + */ + public com.google.protobuf.ByteString + getBuildUserBytes() { + java.lang.Object ref = buildUser_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + buildUser_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string buildUser = 11; + * @param value The buildUser to set. + * @return This builder for chaining. + */ + public Builder setBuildUser( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + buildUser_ = value; + onChanged(); + return this; + } + /** + * string buildUser = 11; + * @return This builder for chaining. + */ + public Builder clearBuildUser() { + + buildUser_ = getDefaultInstance().getBuildUser(); + onChanged(); + return this; + } + /** + * string buildUser = 11; + * @param value The bytes for buildUser to set. + * @return This builder for chaining. + */ + public Builder setBuildUserBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + buildUser_ = value; + onChanged(); + return this; + } + + private long estimatedHeight_ ; + /** + *
+       * less than tip height if pirated is syncing
+       * 
+ * + * uint64 estimatedHeight = 12; + * @return The estimatedHeight. + */ + @java.lang.Override + public long getEstimatedHeight() { + return estimatedHeight_; + } + /** + *
+       * less than tip height if pirated is syncing
+       * 
+ * + * uint64 estimatedHeight = 12; + * @param value The estimatedHeight to set. + * @return This builder for chaining. + */ + public Builder setEstimatedHeight(long value) { + + estimatedHeight_ = value; + onChanged(); + return this; + } + /** + *
+       * less than tip height if pirated is syncing
+       * 
+ * + * uint64 estimatedHeight = 12; + * @return This builder for chaining. + */ + public Builder clearEstimatedHeight() { + + estimatedHeight_ = 0L; + onChanged(); + return this; + } + + private java.lang.Object piratedBuild_ = ""; + /** + *
+       * example: "v4.1.1-877212414"
+       * 
+ * + * string piratedBuild = 13; + * @return The piratedBuild. + */ + public java.lang.String getPiratedBuild() { + java.lang.Object ref = piratedBuild_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + piratedBuild_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * example: "v4.1.1-877212414"
+       * 
+ * + * string piratedBuild = 13; + * @return The bytes for piratedBuild. + */ + public com.google.protobuf.ByteString + getPiratedBuildBytes() { + java.lang.Object ref = piratedBuild_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + piratedBuild_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * example: "v4.1.1-877212414"
+       * 
+ * + * string piratedBuild = 13; + * @param value The piratedBuild to set. + * @return This builder for chaining. + */ + public Builder setPiratedBuild( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + piratedBuild_ = value; + onChanged(); + return this; + } + /** + *
+       * example: "v4.1.1-877212414"
+       * 
+ * + * string piratedBuild = 13; + * @return This builder for chaining. + */ + public Builder clearPiratedBuild() { + + piratedBuild_ = getDefaultInstance().getPiratedBuild(); + onChanged(); + return this; + } + /** + *
+       * example: "v4.1.1-877212414"
+       * 
+ * + * string piratedBuild = 13; + * @param value The bytes for piratedBuild to set. + * @return This builder for chaining. + */ + public Builder setPiratedBuildBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + piratedBuild_ = value; + onChanged(); + return this; + } + + private java.lang.Object piratedSubversion_ = ""; + /** + *
+       * example: "/MagicBean:4.1.1/"
+       * 
+ * + * string piratedSubversion = 14; + * @return The piratedSubversion. + */ + public java.lang.String getPiratedSubversion() { + java.lang.Object ref = piratedSubversion_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + piratedSubversion_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * example: "/MagicBean:4.1.1/"
+       * 
+ * + * string piratedSubversion = 14; + * @return The bytes for piratedSubversion. + */ + public com.google.protobuf.ByteString + getPiratedSubversionBytes() { + java.lang.Object ref = piratedSubversion_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + piratedSubversion_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * example: "/MagicBean:4.1.1/"
+       * 
+ * + * string piratedSubversion = 14; + * @param value The piratedSubversion to set. + * @return This builder for chaining. + */ + public Builder setPiratedSubversion( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + piratedSubversion_ = value; + onChanged(); + return this; + } + /** + *
+       * example: "/MagicBean:4.1.1/"
+       * 
+ * + * string piratedSubversion = 14; + * @return This builder for chaining. + */ + public Builder clearPiratedSubversion() { + + piratedSubversion_ = getDefaultInstance().getPiratedSubversion(); + onChanged(); + return this; + } + /** + *
+       * example: "/MagicBean:4.1.1/"
+       * 
+ * + * string piratedSubversion = 14; + * @param value The bytes for piratedSubversion to set. + * @return This builder for chaining. + */ + public Builder setPiratedSubversionBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + piratedSubversion_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.LightdInfo) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.LightdInfo) + private static final cash.z.wallet.sdk.rpc.Service.LightdInfo DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.LightdInfo(); + } + + public static cash.z.wallet.sdk.rpc.Service.LightdInfo getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public LightdInfo parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new LightdInfo(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.LightdInfo getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface TransparentAddressBlockFilterOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.TransparentAddressBlockFilter) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * t-address
+     * 
+ * + * string address = 1; + * @return The address. + */ + java.lang.String getAddress(); + /** + *
+     * t-address
+     * 
+ * + * string address = 1; + * @return The bytes for address. + */ + com.google.protobuf.ByteString + getAddressBytes(); + + /** + *
+     * start, end heights
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + * @return Whether the range field is set. + */ + boolean hasRange(); + /** + *
+     * start, end heights
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + * @return The range. + */ + cash.z.wallet.sdk.rpc.Service.BlockRange getRange(); + /** + *
+     * start, end heights
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + cash.z.wallet.sdk.rpc.Service.BlockRangeOrBuilder getRangeOrBuilder(); + } + /** + *
+   * TransparentAddressBlockFilter restricts the results to the given address
+   * or block range.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.TransparentAddressBlockFilter} + */ + public static final class TransparentAddressBlockFilter extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.TransparentAddressBlockFilter) + TransparentAddressBlockFilterOrBuilder { + private static final long serialVersionUID = 0L; + // Use TransparentAddressBlockFilter.newBuilder() to construct. + private TransparentAddressBlockFilter(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private TransparentAddressBlockFilter() { + address_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new TransparentAddressBlockFilter(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private TransparentAddressBlockFilter( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + java.lang.String s = input.readStringRequireUtf8(); + + address_ = s; + break; + } + case 18: { + cash.z.wallet.sdk.rpc.Service.BlockRange.Builder subBuilder = null; + if (range_ != null) { + subBuilder = range_.toBuilder(); + } + range_ = input.readMessage(cash.z.wallet.sdk.rpc.Service.BlockRange.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(range_); + range_ = subBuilder.buildPartial(); + } + + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.class, cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.Builder.class); + } + + public static final int ADDRESS_FIELD_NUMBER = 1; + private volatile java.lang.Object address_; + /** + *
+     * t-address
+     * 
+ * + * string address = 1; + * @return The address. + */ + @java.lang.Override + public java.lang.String getAddress() { + java.lang.Object ref = address_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + address_ = s; + return s; + } + } + /** + *
+     * t-address
+     * 
+ * + * string address = 1; + * @return The bytes for address. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getAddressBytes() { + java.lang.Object ref = address_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + address_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int RANGE_FIELD_NUMBER = 2; + private cash.z.wallet.sdk.rpc.Service.BlockRange range_; + /** + *
+     * start, end heights
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + * @return Whether the range field is set. + */ + @java.lang.Override + public boolean hasRange() { + return range_ != null; + } + /** + *
+     * start, end heights
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + * @return The range. + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockRange getRange() { + return range_ == null ? cash.z.wallet.sdk.rpc.Service.BlockRange.getDefaultInstance() : range_; + } + /** + *
+     * start, end heights
+     * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.BlockRangeOrBuilder getRangeOrBuilder() { + return getRange(); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(address_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, address_); + } + if (range_ != null) { + output.writeMessage(2, getRange()); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(address_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, address_); + } + if (range_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, getRange()); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter other = (cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter) obj; + + if (!getAddress() + .equals(other.getAddress())) return false; + if (hasRange() != other.hasRange()) return false; + if (hasRange()) { + if (!getRange() + .equals(other.getRange())) return false; + } + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ADDRESS_FIELD_NUMBER; + hash = (53 * hash) + getAddress().hashCode(); + if (hasRange()) { + hash = (37 * hash) + RANGE_FIELD_NUMBER; + hash = (53 * hash) + getRange().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * TransparentAddressBlockFilter restricts the results to the given address
+     * or block range.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.TransparentAddressBlockFilter} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.TransparentAddressBlockFilter) + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilterOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.class, cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + address_ = ""; + + if (rangeBuilder_ == null) { + range_ = null; + } else { + range_ = null; + rangeBuilder_ = null; + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter build() { + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter buildPartial() { + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter result = new cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter(this); + result.address_ = address_; + if (rangeBuilder_ == null) { + result.range_ = range_; + } else { + result.range_ = rangeBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter other) { + if (other == cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter.getDefaultInstance()) return this; + if (!other.getAddress().isEmpty()) { + address_ = other.address_; + onChanged(); + } + if (other.hasRange()) { + mergeRange(other.getRange()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private java.lang.Object address_ = ""; + /** + *
+       * t-address
+       * 
+ * + * string address = 1; + * @return The address. + */ + public java.lang.String getAddress() { + java.lang.Object ref = address_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + address_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * t-address
+       * 
+ * + * string address = 1; + * @return The bytes for address. + */ + public com.google.protobuf.ByteString + getAddressBytes() { + java.lang.Object ref = address_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + address_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * t-address
+       * 
+ * + * string address = 1; + * @param value The address to set. + * @return This builder for chaining. + */ + public Builder setAddress( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + address_ = value; + onChanged(); + return this; + } + /** + *
+       * t-address
+       * 
+ * + * string address = 1; + * @return This builder for chaining. + */ + public Builder clearAddress() { + + address_ = getDefaultInstance().getAddress(); + onChanged(); + return this; + } + /** + *
+       * t-address
+       * 
+ * + * string address = 1; + * @param value The bytes for address to set. + * @return This builder for chaining. + */ + public Builder setAddressBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + address_ = value; + onChanged(); + return this; + } + + private cash.z.wallet.sdk.rpc.Service.BlockRange range_; + private com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockRange, cash.z.wallet.sdk.rpc.Service.BlockRange.Builder, cash.z.wallet.sdk.rpc.Service.BlockRangeOrBuilder> rangeBuilder_; + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + * @return Whether the range field is set. + */ + public boolean hasRange() { + return rangeBuilder_ != null || range_ != null; + } + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + * @return The range. + */ + public cash.z.wallet.sdk.rpc.Service.BlockRange getRange() { + if (rangeBuilder_ == null) { + return range_ == null ? cash.z.wallet.sdk.rpc.Service.BlockRange.getDefaultInstance() : range_; + } else { + return rangeBuilder_.getMessage(); + } + } + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + public Builder setRange(cash.z.wallet.sdk.rpc.Service.BlockRange value) { + if (rangeBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + range_ = value; + onChanged(); + } else { + rangeBuilder_.setMessage(value); + } + + return this; + } + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + public Builder setRange( + cash.z.wallet.sdk.rpc.Service.BlockRange.Builder builderForValue) { + if (rangeBuilder_ == null) { + range_ = builderForValue.build(); + onChanged(); + } else { + rangeBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + public Builder mergeRange(cash.z.wallet.sdk.rpc.Service.BlockRange value) { + if (rangeBuilder_ == null) { + if (range_ != null) { + range_ = + cash.z.wallet.sdk.rpc.Service.BlockRange.newBuilder(range_).mergeFrom(value).buildPartial(); + } else { + range_ = value; + } + onChanged(); + } else { + rangeBuilder_.mergeFrom(value); + } + + return this; + } + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + public Builder clearRange() { + if (rangeBuilder_ == null) { + range_ = null; + onChanged(); + } else { + range_ = null; + rangeBuilder_ = null; + } + + return this; + } + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + public cash.z.wallet.sdk.rpc.Service.BlockRange.Builder getRangeBuilder() { + + onChanged(); + return getRangeFieldBuilder().getBuilder(); + } + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + public cash.z.wallet.sdk.rpc.Service.BlockRangeOrBuilder getRangeOrBuilder() { + if (rangeBuilder_ != null) { + return rangeBuilder_.getMessageOrBuilder(); + } else { + return range_ == null ? + cash.z.wallet.sdk.rpc.Service.BlockRange.getDefaultInstance() : range_; + } + } + /** + *
+       * start, end heights
+       * 
+ * + * .cash.z.wallet.sdk.rpc.BlockRange range = 2; + */ + private com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockRange, cash.z.wallet.sdk.rpc.Service.BlockRange.Builder, cash.z.wallet.sdk.rpc.Service.BlockRangeOrBuilder> + getRangeFieldBuilder() { + if (rangeBuilder_ == null) { + rangeBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.BlockRange, cash.z.wallet.sdk.rpc.Service.BlockRange.Builder, cash.z.wallet.sdk.rpc.Service.BlockRangeOrBuilder>( + getRange(), + getParentForChildren(), + isClean()); + range_ = null; + } + return rangeBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.TransparentAddressBlockFilter) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.TransparentAddressBlockFilter) + private static final cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter(); + } + + public static cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public TransparentAddressBlockFilter parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new TransparentAddressBlockFilter(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TransparentAddressBlockFilter getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface DurationOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.Duration) + com.google.protobuf.MessageOrBuilder { + + /** + * int64 intervalUs = 1; + * @return The intervalUs. + */ + long getIntervalUs(); + } + /** + *
+   * Duration is currently used only for testing, so that the Ping rpc
+   * can simulate a delay, to create many simultaneous connections. Units
+   * are microseconds.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.Duration} + */ + public static final class Duration extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.Duration) + DurationOrBuilder { + private static final long serialVersionUID = 0L; + // Use Duration.newBuilder() to construct. + private Duration(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Duration() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Duration(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Duration( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + intervalUs_ = input.readInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Duration_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Duration_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Duration.class, cash.z.wallet.sdk.rpc.Service.Duration.Builder.class); + } + + public static final int INTERVALUS_FIELD_NUMBER = 1; + private long intervalUs_; + /** + * int64 intervalUs = 1; + * @return The intervalUs. + */ + @java.lang.Override + public long getIntervalUs() { + return intervalUs_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (intervalUs_ != 0L) { + output.writeInt64(1, intervalUs_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (intervalUs_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(1, intervalUs_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.Duration)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.Duration other = (cash.z.wallet.sdk.rpc.Service.Duration) obj; + + if (getIntervalUs() + != other.getIntervalUs()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + INTERVALUS_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getIntervalUs()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Duration parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.Duration prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * Duration is currently used only for testing, so that the Ping rpc
+     * can simulate a delay, to create many simultaneous connections. Units
+     * are microseconds.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.Duration} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.Duration) + cash.z.wallet.sdk.rpc.Service.DurationOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Duration_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Duration_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Duration.class, cash.z.wallet.sdk.rpc.Service.Duration.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.Duration.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + intervalUs_ = 0L; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Duration_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Duration getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.Duration.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Duration build() { + cash.z.wallet.sdk.rpc.Service.Duration result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Duration buildPartial() { + cash.z.wallet.sdk.rpc.Service.Duration result = new cash.z.wallet.sdk.rpc.Service.Duration(this); + result.intervalUs_ = intervalUs_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.Duration) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.Duration)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.Duration other) { + if (other == cash.z.wallet.sdk.rpc.Service.Duration.getDefaultInstance()) return this; + if (other.getIntervalUs() != 0L) { + setIntervalUs(other.getIntervalUs()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.Duration parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.Duration) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private long intervalUs_ ; + /** + * int64 intervalUs = 1; + * @return The intervalUs. + */ + @java.lang.Override + public long getIntervalUs() { + return intervalUs_; + } + /** + * int64 intervalUs = 1; + * @param value The intervalUs to set. + * @return This builder for chaining. + */ + public Builder setIntervalUs(long value) { + + intervalUs_ = value; + onChanged(); + return this; + } + /** + * int64 intervalUs = 1; + * @return This builder for chaining. + */ + public Builder clearIntervalUs() { + + intervalUs_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.Duration) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.Duration) + private static final cash.z.wallet.sdk.rpc.Service.Duration DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.Duration(); + } + + public static cash.z.wallet.sdk.rpc.Service.Duration getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Duration parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Duration(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Duration getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface PingResponseOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.PingResponse) + com.google.protobuf.MessageOrBuilder { + + /** + * int64 entry = 1; + * @return The entry. + */ + long getEntry(); + + /** + * int64 exit = 2; + * @return The exit. + */ + long getExit(); + } + /** + *
+   * PingResponse is used to indicate concurrency, how many Ping rpcs
+   * are executing upon entry and upon exit (after the delay).
+   * This rpc is used for testing only.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.PingResponse} + */ + public static final class PingResponse extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.PingResponse) + PingResponseOrBuilder { + private static final long serialVersionUID = 0L; + // Use PingResponse.newBuilder() to construct. + private PingResponse(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private PingResponse() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new PingResponse(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private PingResponse( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + entry_ = input.readInt64(); + break; + } + case 16: { + + exit_ = input.readInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_PingResponse_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_PingResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.PingResponse.class, cash.z.wallet.sdk.rpc.Service.PingResponse.Builder.class); + } + + public static final int ENTRY_FIELD_NUMBER = 1; + private long entry_; + /** + * int64 entry = 1; + * @return The entry. + */ + @java.lang.Override + public long getEntry() { + return entry_; + } + + public static final int EXIT_FIELD_NUMBER = 2; + private long exit_; + /** + * int64 exit = 2; + * @return The exit. + */ + @java.lang.Override + public long getExit() { + return exit_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (entry_ != 0L) { + output.writeInt64(1, entry_); + } + if (exit_ != 0L) { + output.writeInt64(2, exit_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (entry_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(1, entry_); + } + if (exit_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(2, exit_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.PingResponse)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.PingResponse other = (cash.z.wallet.sdk.rpc.Service.PingResponse) obj; + + if (getEntry() + != other.getEntry()) return false; + if (getExit() + != other.getExit()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ENTRY_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getEntry()); + hash = (37 * hash) + EXIT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getExit()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.PingResponse parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.PingResponse prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * PingResponse is used to indicate concurrency, how many Ping rpcs
+     * are executing upon entry and upon exit (after the delay).
+     * This rpc is used for testing only.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.PingResponse} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.PingResponse) + cash.z.wallet.sdk.rpc.Service.PingResponseOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_PingResponse_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_PingResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.PingResponse.class, cash.z.wallet.sdk.rpc.Service.PingResponse.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.PingResponse.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + entry_ = 0L; + + exit_ = 0L; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_PingResponse_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.PingResponse getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.PingResponse.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.PingResponse build() { + cash.z.wallet.sdk.rpc.Service.PingResponse result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.PingResponse buildPartial() { + cash.z.wallet.sdk.rpc.Service.PingResponse result = new cash.z.wallet.sdk.rpc.Service.PingResponse(this); + result.entry_ = entry_; + result.exit_ = exit_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.PingResponse) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.PingResponse)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.PingResponse other) { + if (other == cash.z.wallet.sdk.rpc.Service.PingResponse.getDefaultInstance()) return this; + if (other.getEntry() != 0L) { + setEntry(other.getEntry()); + } + if (other.getExit() != 0L) { + setExit(other.getExit()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.PingResponse parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.PingResponse) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private long entry_ ; + /** + * int64 entry = 1; + * @return The entry. + */ + @java.lang.Override + public long getEntry() { + return entry_; + } + /** + * int64 entry = 1; + * @param value The entry to set. + * @return This builder for chaining. + */ + public Builder setEntry(long value) { + + entry_ = value; + onChanged(); + return this; + } + /** + * int64 entry = 1; + * @return This builder for chaining. + */ + public Builder clearEntry() { + + entry_ = 0L; + onChanged(); + return this; + } + + private long exit_ ; + /** + * int64 exit = 2; + * @return The exit. + */ + @java.lang.Override + public long getExit() { + return exit_; + } + /** + * int64 exit = 2; + * @param value The exit to set. + * @return This builder for chaining. + */ + public Builder setExit(long value) { + + exit_ = value; + onChanged(); + return this; + } + /** + * int64 exit = 2; + * @return This builder for chaining. + */ + public Builder clearExit() { + + exit_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.PingResponse) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.PingResponse) + private static final cash.z.wallet.sdk.rpc.Service.PingResponse DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.PingResponse(); + } + + public static cash.z.wallet.sdk.rpc.Service.PingResponse getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public PingResponse parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new PingResponse(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.PingResponse getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AddressOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.Address) + com.google.protobuf.MessageOrBuilder { + + /** + * string address = 1; + * @return The address. + */ + java.lang.String getAddress(); + /** + * string address = 1; + * @return The bytes for address. + */ + com.google.protobuf.ByteString + getAddressBytes(); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.Address} + */ + public static final class Address extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.Address) + AddressOrBuilder { + private static final long serialVersionUID = 0L; + // Use Address.newBuilder() to construct. + private Address(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Address() { + address_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Address(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Address( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + java.lang.String s = input.readStringRequireUtf8(); + + address_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Address_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Address_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Address.class, cash.z.wallet.sdk.rpc.Service.Address.Builder.class); + } + + public static final int ADDRESS_FIELD_NUMBER = 1; + private volatile java.lang.Object address_; + /** + * string address = 1; + * @return The address. + */ + @java.lang.Override + public java.lang.String getAddress() { + java.lang.Object ref = address_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + address_ = s; + return s; + } + } + /** + * string address = 1; + * @return The bytes for address. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getAddressBytes() { + java.lang.Object ref = address_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + address_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(address_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, address_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(address_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, address_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.Address)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.Address other = (cash.z.wallet.sdk.rpc.Service.Address) obj; + + if (!getAddress() + .equals(other.getAddress())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ADDRESS_FIELD_NUMBER; + hash = (53 * hash) + getAddress().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Address parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.Address prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.Address} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.Address) + cash.z.wallet.sdk.rpc.Service.AddressOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Address_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Address_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Address.class, cash.z.wallet.sdk.rpc.Service.Address.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.Address.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + address_ = ""; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Address_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Address getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.Address.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Address build() { + cash.z.wallet.sdk.rpc.Service.Address result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Address buildPartial() { + cash.z.wallet.sdk.rpc.Service.Address result = new cash.z.wallet.sdk.rpc.Service.Address(this); + result.address_ = address_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.Address) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.Address)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.Address other) { + if (other == cash.z.wallet.sdk.rpc.Service.Address.getDefaultInstance()) return this; + if (!other.getAddress().isEmpty()) { + address_ = other.address_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.Address parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.Address) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private java.lang.Object address_ = ""; + /** + * string address = 1; + * @return The address. + */ + public java.lang.String getAddress() { + java.lang.Object ref = address_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + address_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string address = 1; + * @return The bytes for address. + */ + public com.google.protobuf.ByteString + getAddressBytes() { + java.lang.Object ref = address_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + address_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string address = 1; + * @param value The address to set. + * @return This builder for chaining. + */ + public Builder setAddress( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + address_ = value; + onChanged(); + return this; + } + /** + * string address = 1; + * @return This builder for chaining. + */ + public Builder clearAddress() { + + address_ = getDefaultInstance().getAddress(); + onChanged(); + return this; + } + /** + * string address = 1; + * @param value The bytes for address to set. + * @return This builder for chaining. + */ + public Builder setAddressBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + address_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.Address) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.Address) + private static final cash.z.wallet.sdk.rpc.Service.Address DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.Address(); + } + + public static cash.z.wallet.sdk.rpc.Service.Address getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser
+ PARSER = new com.google.protobuf.AbstractParser
() { + @java.lang.Override + public Address parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Address(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser
parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser
getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Address getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AddressListOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.AddressList) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated string addresses = 1; + * @return A list containing the addresses. + */ + java.util.List + getAddressesList(); + /** + * repeated string addresses = 1; + * @return The count of addresses. + */ + int getAddressesCount(); + /** + * repeated string addresses = 1; + * @param index The index of the element to return. + * @return The addresses at the given index. + */ + java.lang.String getAddresses(int index); + /** + * repeated string addresses = 1; + * @param index The index of the value to return. + * @return The bytes of the addresses at the given index. + */ + com.google.protobuf.ByteString + getAddressesBytes(int index); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.AddressList} + */ + public static final class AddressList extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.AddressList) + AddressListOrBuilder { + private static final long serialVersionUID = 0L; + // Use AddressList.newBuilder() to construct. + private AddressList(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private AddressList() { + addresses_ = com.google.protobuf.LazyStringArrayList.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new AddressList(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private AddressList( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + addresses_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00000001; + } + addresses_.add(s); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + addresses_ = addresses_.getUnmodifiableView(); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_AddressList_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_AddressList_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.AddressList.class, cash.z.wallet.sdk.rpc.Service.AddressList.Builder.class); + } + + public static final int ADDRESSES_FIELD_NUMBER = 1; + private com.google.protobuf.LazyStringList addresses_; + /** + * repeated string addresses = 1; + * @return A list containing the addresses. + */ + public com.google.protobuf.ProtocolStringList + getAddressesList() { + return addresses_; + } + /** + * repeated string addresses = 1; + * @return The count of addresses. + */ + public int getAddressesCount() { + return addresses_.size(); + } + /** + * repeated string addresses = 1; + * @param index The index of the element to return. + * @return The addresses at the given index. + */ + public java.lang.String getAddresses(int index) { + return addresses_.get(index); + } + /** + * repeated string addresses = 1; + * @param index The index of the value to return. + * @return The bytes of the addresses at the given index. + */ + public com.google.protobuf.ByteString + getAddressesBytes(int index) { + return addresses_.getByteString(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < addresses_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, addresses_.getRaw(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + { + int dataSize = 0; + for (int i = 0; i < addresses_.size(); i++) { + dataSize += computeStringSizeNoTag(addresses_.getRaw(i)); + } + size += dataSize; + size += 1 * getAddressesList().size(); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.AddressList)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.AddressList other = (cash.z.wallet.sdk.rpc.Service.AddressList) obj; + + if (!getAddressesList() + .equals(other.getAddressesList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getAddressesCount() > 0) { + hash = (37 * hash) + ADDRESSES_FIELD_NUMBER; + hash = (53 * hash) + getAddressesList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.AddressList parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.AddressList prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.AddressList} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.AddressList) + cash.z.wallet.sdk.rpc.Service.AddressListOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_AddressList_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_AddressList_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.AddressList.class, cash.z.wallet.sdk.rpc.Service.AddressList.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.AddressList.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + addresses_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_AddressList_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.AddressList getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.AddressList.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.AddressList build() { + cash.z.wallet.sdk.rpc.Service.AddressList result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.AddressList buildPartial() { + cash.z.wallet.sdk.rpc.Service.AddressList result = new cash.z.wallet.sdk.rpc.Service.AddressList(this); + int from_bitField0_ = bitField0_; + if (((bitField0_ & 0x00000001) != 0)) { + addresses_ = addresses_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.addresses_ = addresses_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.AddressList) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.AddressList)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.AddressList other) { + if (other == cash.z.wallet.sdk.rpc.Service.AddressList.getDefaultInstance()) return this; + if (!other.addresses_.isEmpty()) { + if (addresses_.isEmpty()) { + addresses_ = other.addresses_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAddressesIsMutable(); + addresses_.addAll(other.addresses_); + } + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.AddressList parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.AddressList) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private com.google.protobuf.LazyStringList addresses_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureAddressesIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + addresses_ = new com.google.protobuf.LazyStringArrayList(addresses_); + bitField0_ |= 0x00000001; + } + } + /** + * repeated string addresses = 1; + * @return A list containing the addresses. + */ + public com.google.protobuf.ProtocolStringList + getAddressesList() { + return addresses_.getUnmodifiableView(); + } + /** + * repeated string addresses = 1; + * @return The count of addresses. + */ + public int getAddressesCount() { + return addresses_.size(); + } + /** + * repeated string addresses = 1; + * @param index The index of the element to return. + * @return The addresses at the given index. + */ + public java.lang.String getAddresses(int index) { + return addresses_.get(index); + } + /** + * repeated string addresses = 1; + * @param index The index of the value to return. + * @return The bytes of the addresses at the given index. + */ + public com.google.protobuf.ByteString + getAddressesBytes(int index) { + return addresses_.getByteString(index); + } + /** + * repeated string addresses = 1; + * @param index The index to set the value at. + * @param value The addresses to set. + * @return This builder for chaining. + */ + public Builder setAddresses( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAddressesIsMutable(); + addresses_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string addresses = 1; + * @param value The addresses to add. + * @return This builder for chaining. + */ + public Builder addAddresses( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAddressesIsMutable(); + addresses_.add(value); + onChanged(); + return this; + } + /** + * repeated string addresses = 1; + * @param values The addresses to add. + * @return This builder for chaining. + */ + public Builder addAllAddresses( + java.lang.Iterable values) { + ensureAddressesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, addresses_); + onChanged(); + return this; + } + /** + * repeated string addresses = 1; + * @return This builder for chaining. + */ + public Builder clearAddresses() { + addresses_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * repeated string addresses = 1; + * @param value The bytes of the addresses to add. + * @return This builder for chaining. + */ + public Builder addAddressesBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureAddressesIsMutable(); + addresses_.add(value); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.AddressList) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.AddressList) + private static final cash.z.wallet.sdk.rpc.Service.AddressList DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.AddressList(); + } + + public static cash.z.wallet.sdk.rpc.Service.AddressList getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AddressList parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new AddressList(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.AddressList getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface BalanceOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.Balance) + com.google.protobuf.MessageOrBuilder { + + /** + * int64 valueZat = 1; + * @return The valueZat. + */ + long getValueZat(); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.Balance} + */ + public static final class Balance extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.Balance) + BalanceOrBuilder { + private static final long serialVersionUID = 0L; + // Use Balance.newBuilder() to construct. + private Balance(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Balance() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Balance(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Balance( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + valueZat_ = input.readInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Balance_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Balance_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Balance.class, cash.z.wallet.sdk.rpc.Service.Balance.Builder.class); + } + + public static final int VALUEZAT_FIELD_NUMBER = 1; + private long valueZat_; + /** + * int64 valueZat = 1; + * @return The valueZat. + */ + @java.lang.Override + public long getValueZat() { + return valueZat_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (valueZat_ != 0L) { + output.writeInt64(1, valueZat_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (valueZat_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(1, valueZat_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.Balance)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.Balance other = (cash.z.wallet.sdk.rpc.Service.Balance) obj; + + if (getValueZat() + != other.getValueZat()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + VALUEZAT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getValueZat()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Balance parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.Balance prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.Balance} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.Balance) + cash.z.wallet.sdk.rpc.Service.BalanceOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Balance_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Balance_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Balance.class, cash.z.wallet.sdk.rpc.Service.Balance.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.Balance.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + valueZat_ = 0L; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Balance_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Balance getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.Balance.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Balance build() { + cash.z.wallet.sdk.rpc.Service.Balance result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Balance buildPartial() { + cash.z.wallet.sdk.rpc.Service.Balance result = new cash.z.wallet.sdk.rpc.Service.Balance(this); + result.valueZat_ = valueZat_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.Balance) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.Balance)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.Balance other) { + if (other == cash.z.wallet.sdk.rpc.Service.Balance.getDefaultInstance()) return this; + if (other.getValueZat() != 0L) { + setValueZat(other.getValueZat()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.Balance parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.Balance) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private long valueZat_ ; + /** + * int64 valueZat = 1; + * @return The valueZat. + */ + @java.lang.Override + public long getValueZat() { + return valueZat_; + } + /** + * int64 valueZat = 1; + * @param value The valueZat to set. + * @return This builder for chaining. + */ + public Builder setValueZat(long value) { + + valueZat_ = value; + onChanged(); + return this; + } + /** + * int64 valueZat = 1; + * @return This builder for chaining. + */ + public Builder clearValueZat() { + + valueZat_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.Balance) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.Balance) + private static final cash.z.wallet.sdk.rpc.Service.Balance DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.Balance(); + } + + public static cash.z.wallet.sdk.rpc.Service.Balance getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Balance parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Balance(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Balance getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ExcludeOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.Exclude) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated bytes txid = 1; + * @return A list containing the txid. + */ + java.util.List getTxidList(); + /** + * repeated bytes txid = 1; + * @return The count of txid. + */ + int getTxidCount(); + /** + * repeated bytes txid = 1; + * @param index The index of the element to return. + * @return The txid at the given index. + */ + com.google.protobuf.ByteString getTxid(int index); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.Exclude} + */ + public static final class Exclude extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.Exclude) + ExcludeOrBuilder { + private static final long serialVersionUID = 0L; + // Use Exclude.newBuilder() to construct. + private Exclude(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Exclude() { + txid_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Exclude(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Exclude( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + txid_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + txid_.add(input.readBytes()); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + txid_ = java.util.Collections.unmodifiableList(txid_); // C + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Exclude_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Exclude_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Exclude.class, cash.z.wallet.sdk.rpc.Service.Exclude.Builder.class); + } + + public static final int TXID_FIELD_NUMBER = 1; + private java.util.List txid_; + /** + * repeated bytes txid = 1; + * @return A list containing the txid. + */ + @java.lang.Override + public java.util.List + getTxidList() { + return txid_; + } + /** + * repeated bytes txid = 1; + * @return The count of txid. + */ + public int getTxidCount() { + return txid_.size(); + } + /** + * repeated bytes txid = 1; + * @param index The index of the element to return. + * @return The txid at the given index. + */ + public com.google.protobuf.ByteString getTxid(int index) { + return txid_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < txid_.size(); i++) { + output.writeBytes(1, txid_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + { + int dataSize = 0; + for (int i = 0; i < txid_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeBytesSizeNoTag(txid_.get(i)); + } + size += dataSize; + size += 1 * getTxidList().size(); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.Exclude)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.Exclude other = (cash.z.wallet.sdk.rpc.Service.Exclude) obj; + + if (!getTxidList() + .equals(other.getTxidList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getTxidCount() > 0) { + hash = (37 * hash) + TXID_FIELD_NUMBER; + hash = (53 * hash) + getTxidList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.Exclude parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.Exclude prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.Exclude} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.Exclude) + cash.z.wallet.sdk.rpc.Service.ExcludeOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Exclude_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Exclude_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.Exclude.class, cash.z.wallet.sdk.rpc.Service.Exclude.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.Exclude.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + txid_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_Exclude_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Exclude getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.Exclude.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Exclude build() { + cash.z.wallet.sdk.rpc.Service.Exclude result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Exclude buildPartial() { + cash.z.wallet.sdk.rpc.Service.Exclude result = new cash.z.wallet.sdk.rpc.Service.Exclude(this); + int from_bitField0_ = bitField0_; + if (((bitField0_ & 0x00000001) != 0)) { + txid_ = java.util.Collections.unmodifiableList(txid_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.txid_ = txid_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.Exclude) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.Exclude)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.Exclude other) { + if (other == cash.z.wallet.sdk.rpc.Service.Exclude.getDefaultInstance()) return this; + if (!other.txid_.isEmpty()) { + if (txid_.isEmpty()) { + txid_ = other.txid_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureTxidIsMutable(); + txid_.addAll(other.txid_); + } + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.Exclude parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.Exclude) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.util.List txid_ = java.util.Collections.emptyList(); + private void ensureTxidIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + txid_ = new java.util.ArrayList(txid_); + bitField0_ |= 0x00000001; + } + } + /** + * repeated bytes txid = 1; + * @return A list containing the txid. + */ + public java.util.List + getTxidList() { + return ((bitField0_ & 0x00000001) != 0) ? + java.util.Collections.unmodifiableList(txid_) : txid_; + } + /** + * repeated bytes txid = 1; + * @return The count of txid. + */ + public int getTxidCount() { + return txid_.size(); + } + /** + * repeated bytes txid = 1; + * @param index The index of the element to return. + * @return The txid at the given index. + */ + public com.google.protobuf.ByteString getTxid(int index) { + return txid_.get(index); + } + /** + * repeated bytes txid = 1; + * @param index The index to set the value at. + * @param value The txid to set. + * @return This builder for chaining. + */ + public Builder setTxid( + int index, com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + ensureTxidIsMutable(); + txid_.set(index, value); + onChanged(); + return this; + } + /** + * repeated bytes txid = 1; + * @param value The txid to add. + * @return This builder for chaining. + */ + public Builder addTxid(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + ensureTxidIsMutable(); + txid_.add(value); + onChanged(); + return this; + } + /** + * repeated bytes txid = 1; + * @param values The txid to add. + * @return This builder for chaining. + */ + public Builder addAllTxid( + java.lang.Iterable values) { + ensureTxidIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, txid_); + onChanged(); + return this; + } + /** + * repeated bytes txid = 1; + * @return This builder for chaining. + */ + public Builder clearTxid() { + txid_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.Exclude) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.Exclude) + private static final cash.z.wallet.sdk.rpc.Service.Exclude DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.Exclude(); + } + + public static cash.z.wallet.sdk.rpc.Service.Exclude getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Exclude parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Exclude(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.Exclude getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface TreeStateOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.TreeState) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * "main" or "test"
+     * 
+ * + * string network = 1; + * @return The network. + */ + java.lang.String getNetwork(); + /** + *
+     * "main" or "test"
+     * 
+ * + * string network = 1; + * @return The bytes for network. + */ + com.google.protobuf.ByteString + getNetworkBytes(); + + /** + * uint64 height = 2; + * @return The height. + */ + long getHeight(); + + /** + *
+     * block id
+     * 
+ * + * string hash = 3; + * @return The hash. + */ + java.lang.String getHash(); + /** + *
+     * block id
+     * 
+ * + * string hash = 3; + * @return The bytes for hash. + */ + com.google.protobuf.ByteString + getHashBytes(); + + /** + *
+     * Unix epoch time when the block was mined
+     * 
+ * + * uint32 time = 4; + * @return The time. + */ + int getTime(); + + /** + *
+     * sapling commitment tree state
+     * 
+ * + * string tree = 5; + * @return The tree. + */ + java.lang.String getTree(); + /** + *
+     * sapling commitment tree state
+     * 
+ * + * string tree = 5; + * @return The bytes for tree. + */ + com.google.protobuf.ByteString + getTreeBytes(); + } + /** + *
+   * The TreeState is derived from the Zcash z_gettreestate rpc.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.TreeState} + */ + public static final class TreeState extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.TreeState) + TreeStateOrBuilder { + private static final long serialVersionUID = 0L; + // Use TreeState.newBuilder() to construct. + private TreeState(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private TreeState() { + network_ = ""; + hash_ = ""; + tree_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new TreeState(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private TreeState( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + java.lang.String s = input.readStringRequireUtf8(); + + network_ = s; + break; + } + case 16: { + + height_ = input.readUInt64(); + break; + } + case 26: { + java.lang.String s = input.readStringRequireUtf8(); + + hash_ = s; + break; + } + case 32: { + + time_ = input.readUInt32(); + break; + } + case 42: { + java.lang.String s = input.readStringRequireUtf8(); + + tree_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TreeState_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TreeState_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.TreeState.class, cash.z.wallet.sdk.rpc.Service.TreeState.Builder.class); + } + + public static final int NETWORK_FIELD_NUMBER = 1; + private volatile java.lang.Object network_; + /** + *
+     * "main" or "test"
+     * 
+ * + * string network = 1; + * @return The network. + */ + @java.lang.Override + public java.lang.String getNetwork() { + java.lang.Object ref = network_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + network_ = s; + return s; + } + } + /** + *
+     * "main" or "test"
+     * 
+ * + * string network = 1; + * @return The bytes for network. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getNetworkBytes() { + java.lang.Object ref = network_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + network_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int HEIGHT_FIELD_NUMBER = 2; + private long height_; + /** + * uint64 height = 2; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + + public static final int HASH_FIELD_NUMBER = 3; + private volatile java.lang.Object hash_; + /** + *
+     * block id
+     * 
+ * + * string hash = 3; + * @return The hash. + */ + @java.lang.Override + public java.lang.String getHash() { + java.lang.Object ref = hash_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + hash_ = s; + return s; + } + } + /** + *
+     * block id
+     * 
+ * + * string hash = 3; + * @return The bytes for hash. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getHashBytes() { + java.lang.Object ref = hash_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + hash_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int TIME_FIELD_NUMBER = 4; + private int time_; + /** + *
+     * Unix epoch time when the block was mined
+     * 
+ * + * uint32 time = 4; + * @return The time. + */ + @java.lang.Override + public int getTime() { + return time_; + } + + public static final int TREE_FIELD_NUMBER = 5; + private volatile java.lang.Object tree_; + /** + *
+     * sapling commitment tree state
+     * 
+ * + * string tree = 5; + * @return The tree. + */ + @java.lang.Override + public java.lang.String getTree() { + java.lang.Object ref = tree_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + tree_ = s; + return s; + } + } + /** + *
+     * sapling commitment tree state
+     * 
+ * + * string tree = 5; + * @return The bytes for tree. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getTreeBytes() { + java.lang.Object ref = tree_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + tree_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(network_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, network_); + } + if (height_ != 0L) { + output.writeUInt64(2, height_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(hash_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 3, hash_); + } + if (time_ != 0) { + output.writeUInt32(4, time_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(tree_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 5, tree_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(network_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, network_); + } + if (height_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(2, height_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(hash_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, hash_); + } + if (time_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(4, time_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(tree_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, tree_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.TreeState)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.TreeState other = (cash.z.wallet.sdk.rpc.Service.TreeState) obj; + + if (!getNetwork() + .equals(other.getNetwork())) return false; + if (getHeight() + != other.getHeight()) return false; + if (!getHash() + .equals(other.getHash())) return false; + if (getTime() + != other.getTime()) return false; + if (!getTree() + .equals(other.getTree())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + NETWORK_FIELD_NUMBER; + hash = (53 * hash) + getNetwork().hashCode(); + hash = (37 * hash) + HEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getHeight()); + hash = (37 * hash) + HASH_FIELD_NUMBER; + hash = (53 * hash) + getHash().hashCode(); + hash = (37 * hash) + TIME_FIELD_NUMBER; + hash = (53 * hash) + getTime(); + hash = (37 * hash) + TREE_FIELD_NUMBER; + hash = (53 * hash) + getTree().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.TreeState parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.TreeState prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * The TreeState is derived from the Zcash z_gettreestate rpc.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.TreeState} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.TreeState) + cash.z.wallet.sdk.rpc.Service.TreeStateOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TreeState_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TreeState_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.TreeState.class, cash.z.wallet.sdk.rpc.Service.TreeState.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.TreeState.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + network_ = ""; + + height_ = 0L; + + hash_ = ""; + + time_ = 0; + + tree_ = ""; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_TreeState_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TreeState getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.TreeState.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TreeState build() { + cash.z.wallet.sdk.rpc.Service.TreeState result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TreeState buildPartial() { + cash.z.wallet.sdk.rpc.Service.TreeState result = new cash.z.wallet.sdk.rpc.Service.TreeState(this); + result.network_ = network_; + result.height_ = height_; + result.hash_ = hash_; + result.time_ = time_; + result.tree_ = tree_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.TreeState) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.TreeState)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.TreeState other) { + if (other == cash.z.wallet.sdk.rpc.Service.TreeState.getDefaultInstance()) return this; + if (!other.getNetwork().isEmpty()) { + network_ = other.network_; + onChanged(); + } + if (other.getHeight() != 0L) { + setHeight(other.getHeight()); + } + if (!other.getHash().isEmpty()) { + hash_ = other.hash_; + onChanged(); + } + if (other.getTime() != 0) { + setTime(other.getTime()); + } + if (!other.getTree().isEmpty()) { + tree_ = other.tree_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.TreeState parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.TreeState) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private java.lang.Object network_ = ""; + /** + *
+       * "main" or "test"
+       * 
+ * + * string network = 1; + * @return The network. + */ + public java.lang.String getNetwork() { + java.lang.Object ref = network_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + network_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * "main" or "test"
+       * 
+ * + * string network = 1; + * @return The bytes for network. + */ + public com.google.protobuf.ByteString + getNetworkBytes() { + java.lang.Object ref = network_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + network_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * "main" or "test"
+       * 
+ * + * string network = 1; + * @param value The network to set. + * @return This builder for chaining. + */ + public Builder setNetwork( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + network_ = value; + onChanged(); + return this; + } + /** + *
+       * "main" or "test"
+       * 
+ * + * string network = 1; + * @return This builder for chaining. + */ + public Builder clearNetwork() { + + network_ = getDefaultInstance().getNetwork(); + onChanged(); + return this; + } + /** + *
+       * "main" or "test"
+       * 
+ * + * string network = 1; + * @param value The bytes for network to set. + * @return This builder for chaining. + */ + public Builder setNetworkBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + network_ = value; + onChanged(); + return this; + } + + private long height_ ; + /** + * uint64 height = 2; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + /** + * uint64 height = 2; + * @param value The height to set. + * @return This builder for chaining. + */ + public Builder setHeight(long value) { + + height_ = value; + onChanged(); + return this; + } + /** + * uint64 height = 2; + * @return This builder for chaining. + */ + public Builder clearHeight() { + + height_ = 0L; + onChanged(); + return this; + } + + private java.lang.Object hash_ = ""; + /** + *
+       * block id
+       * 
+ * + * string hash = 3; + * @return The hash. + */ + public java.lang.String getHash() { + java.lang.Object ref = hash_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + hash_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * block id
+       * 
+ * + * string hash = 3; + * @return The bytes for hash. + */ + public com.google.protobuf.ByteString + getHashBytes() { + java.lang.Object ref = hash_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + hash_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * block id
+       * 
+ * + * string hash = 3; + * @param value The hash to set. + * @return This builder for chaining. + */ + public Builder setHash( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + hash_ = value; + onChanged(); + return this; + } + /** + *
+       * block id
+       * 
+ * + * string hash = 3; + * @return This builder for chaining. + */ + public Builder clearHash() { + + hash_ = getDefaultInstance().getHash(); + onChanged(); + return this; + } + /** + *
+       * block id
+       * 
+ * + * string hash = 3; + * @param value The bytes for hash to set. + * @return This builder for chaining. + */ + public Builder setHashBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + hash_ = value; + onChanged(); + return this; + } + + private int time_ ; + /** + *
+       * Unix epoch time when the block was mined
+       * 
+ * + * uint32 time = 4; + * @return The time. + */ + @java.lang.Override + public int getTime() { + return time_; + } + /** + *
+       * Unix epoch time when the block was mined
+       * 
+ * + * uint32 time = 4; + * @param value The time to set. + * @return This builder for chaining. + */ + public Builder setTime(int value) { + + time_ = value; + onChanged(); + return this; + } + /** + *
+       * Unix epoch time when the block was mined
+       * 
+ * + * uint32 time = 4; + * @return This builder for chaining. + */ + public Builder clearTime() { + + time_ = 0; + onChanged(); + return this; + } + + private java.lang.Object tree_ = ""; + /** + *
+       * sapling commitment tree state
+       * 
+ * + * string tree = 5; + * @return The tree. + */ + public java.lang.String getTree() { + java.lang.Object ref = tree_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + tree_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * sapling commitment tree state
+       * 
+ * + * string tree = 5; + * @return The bytes for tree. + */ + public com.google.protobuf.ByteString + getTreeBytes() { + java.lang.Object ref = tree_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + tree_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * sapling commitment tree state
+       * 
+ * + * string tree = 5; + * @param value The tree to set. + * @return This builder for chaining. + */ + public Builder setTree( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + tree_ = value; + onChanged(); + return this; + } + /** + *
+       * sapling commitment tree state
+       * 
+ * + * string tree = 5; + * @return This builder for chaining. + */ + public Builder clearTree() { + + tree_ = getDefaultInstance().getTree(); + onChanged(); + return this; + } + /** + *
+       * sapling commitment tree state
+       * 
+ * + * string tree = 5; + * @param value The bytes for tree to set. + * @return This builder for chaining. + */ + public Builder setTreeBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + tree_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.TreeState) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.TreeState) + private static final cash.z.wallet.sdk.rpc.Service.TreeState DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.TreeState(); + } + + public static cash.z.wallet.sdk.rpc.Service.TreeState getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public TreeState parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new TreeState(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.TreeState getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface GetAddressUtxosArgOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.GetAddressUtxosArg) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated string addresses = 1; + * @return A list containing the addresses. + */ + java.util.List + getAddressesList(); + /** + * repeated string addresses = 1; + * @return The count of addresses. + */ + int getAddressesCount(); + /** + * repeated string addresses = 1; + * @param index The index of the element to return. + * @return The addresses at the given index. + */ + java.lang.String getAddresses(int index); + /** + * repeated string addresses = 1; + * @param index The index of the value to return. + * @return The bytes of the addresses at the given index. + */ + com.google.protobuf.ByteString + getAddressesBytes(int index); + + /** + * uint64 startHeight = 2; + * @return The startHeight. + */ + long getStartHeight(); + + /** + *
+     * zero means unlimited
+     * 
+ * + * uint32 maxEntries = 3; + * @return The maxEntries. + */ + int getMaxEntries(); + } + /** + *
+   * Results are sorted by height, which makes it easy to issue another
+   * request that picks up from where the previous left off.
+   * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.GetAddressUtxosArg} + */ + public static final class GetAddressUtxosArg extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.GetAddressUtxosArg) + GetAddressUtxosArgOrBuilder { + private static final long serialVersionUID = 0L; + // Use GetAddressUtxosArg.newBuilder() to construct. + private GetAddressUtxosArg(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private GetAddressUtxosArg() { + addresses_ = com.google.protobuf.LazyStringArrayList.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new GetAddressUtxosArg(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GetAddressUtxosArg( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + addresses_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00000001; + } + addresses_.add(s); + break; + } + case 16: { + + startHeight_ = input.readUInt64(); + break; + } + case 24: { + + maxEntries_ = input.readUInt32(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + addresses_ = addresses_.getUnmodifiableView(); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.class, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.Builder.class); + } + + public static final int ADDRESSES_FIELD_NUMBER = 1; + private com.google.protobuf.LazyStringList addresses_; + /** + * repeated string addresses = 1; + * @return A list containing the addresses. + */ + public com.google.protobuf.ProtocolStringList + getAddressesList() { + return addresses_; + } + /** + * repeated string addresses = 1; + * @return The count of addresses. + */ + public int getAddressesCount() { + return addresses_.size(); + } + /** + * repeated string addresses = 1; + * @param index The index of the element to return. + * @return The addresses at the given index. + */ + public java.lang.String getAddresses(int index) { + return addresses_.get(index); + } + /** + * repeated string addresses = 1; + * @param index The index of the value to return. + * @return The bytes of the addresses at the given index. + */ + public com.google.protobuf.ByteString + getAddressesBytes(int index) { + return addresses_.getByteString(index); + } + + public static final int STARTHEIGHT_FIELD_NUMBER = 2; + private long startHeight_; + /** + * uint64 startHeight = 2; + * @return The startHeight. + */ + @java.lang.Override + public long getStartHeight() { + return startHeight_; + } + + public static final int MAXENTRIES_FIELD_NUMBER = 3; + private int maxEntries_; + /** + *
+     * zero means unlimited
+     * 
+ * + * uint32 maxEntries = 3; + * @return The maxEntries. + */ + @java.lang.Override + public int getMaxEntries() { + return maxEntries_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < addresses_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, addresses_.getRaw(i)); + } + if (startHeight_ != 0L) { + output.writeUInt64(2, startHeight_); + } + if (maxEntries_ != 0) { + output.writeUInt32(3, maxEntries_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + { + int dataSize = 0; + for (int i = 0; i < addresses_.size(); i++) { + dataSize += computeStringSizeNoTag(addresses_.getRaw(i)); + } + size += dataSize; + size += 1 * getAddressesList().size(); + } + if (startHeight_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(2, startHeight_); + } + if (maxEntries_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(3, maxEntries_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg other = (cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg) obj; + + if (!getAddressesList() + .equals(other.getAddressesList())) return false; + if (getStartHeight() + != other.getStartHeight()) return false; + if (getMaxEntries() + != other.getMaxEntries()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getAddressesCount() > 0) { + hash = (37 * hash) + ADDRESSES_FIELD_NUMBER; + hash = (53 * hash) + getAddressesList().hashCode(); + } + hash = (37 * hash) + STARTHEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getStartHeight()); + hash = (37 * hash) + MAXENTRIES_FIELD_NUMBER; + hash = (53 * hash) + getMaxEntries(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * Results are sorted by height, which makes it easy to issue another
+     * request that picks up from where the previous left off.
+     * 
+ * + * Protobuf type {@code cash.z.wallet.sdk.rpc.GetAddressUtxosArg} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.GetAddressUtxosArg) + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArgOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.class, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + addresses_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + startHeight_ = 0L; + + maxEntries_ = 0; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg build() { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg buildPartial() { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg result = new cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg(this); + int from_bitField0_ = bitField0_; + if (((bitField0_ & 0x00000001) != 0)) { + addresses_ = addresses_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.addresses_ = addresses_; + result.startHeight_ = startHeight_; + result.maxEntries_ = maxEntries_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg other) { + if (other == cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg.getDefaultInstance()) return this; + if (!other.addresses_.isEmpty()) { + if (addresses_.isEmpty()) { + addresses_ = other.addresses_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAddressesIsMutable(); + addresses_.addAll(other.addresses_); + } + onChanged(); + } + if (other.getStartHeight() != 0L) { + setStartHeight(other.getStartHeight()); + } + if (other.getMaxEntries() != 0) { + setMaxEntries(other.getMaxEntries()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private com.google.protobuf.LazyStringList addresses_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureAddressesIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + addresses_ = new com.google.protobuf.LazyStringArrayList(addresses_); + bitField0_ |= 0x00000001; + } + } + /** + * repeated string addresses = 1; + * @return A list containing the addresses. + */ + public com.google.protobuf.ProtocolStringList + getAddressesList() { + return addresses_.getUnmodifiableView(); + } + /** + * repeated string addresses = 1; + * @return The count of addresses. + */ + public int getAddressesCount() { + return addresses_.size(); + } + /** + * repeated string addresses = 1; + * @param index The index of the element to return. + * @return The addresses at the given index. + */ + public java.lang.String getAddresses(int index) { + return addresses_.get(index); + } + /** + * repeated string addresses = 1; + * @param index The index of the value to return. + * @return The bytes of the addresses at the given index. + */ + public com.google.protobuf.ByteString + getAddressesBytes(int index) { + return addresses_.getByteString(index); + } + /** + * repeated string addresses = 1; + * @param index The index to set the value at. + * @param value The addresses to set. + * @return This builder for chaining. + */ + public Builder setAddresses( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAddressesIsMutable(); + addresses_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string addresses = 1; + * @param value The addresses to add. + * @return This builder for chaining. + */ + public Builder addAddresses( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAddressesIsMutable(); + addresses_.add(value); + onChanged(); + return this; + } + /** + * repeated string addresses = 1; + * @param values The addresses to add. + * @return This builder for chaining. + */ + public Builder addAllAddresses( + java.lang.Iterable values) { + ensureAddressesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, addresses_); + onChanged(); + return this; + } + /** + * repeated string addresses = 1; + * @return This builder for chaining. + */ + public Builder clearAddresses() { + addresses_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * repeated string addresses = 1; + * @param value The bytes of the addresses to add. + * @return This builder for chaining. + */ + public Builder addAddressesBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureAddressesIsMutable(); + addresses_.add(value); + onChanged(); + return this; + } + + private long startHeight_ ; + /** + * uint64 startHeight = 2; + * @return The startHeight. + */ + @java.lang.Override + public long getStartHeight() { + return startHeight_; + } + /** + * uint64 startHeight = 2; + * @param value The startHeight to set. + * @return This builder for chaining. + */ + public Builder setStartHeight(long value) { + + startHeight_ = value; + onChanged(); + return this; + } + /** + * uint64 startHeight = 2; + * @return This builder for chaining. + */ + public Builder clearStartHeight() { + + startHeight_ = 0L; + onChanged(); + return this; + } + + private int maxEntries_ ; + /** + *
+       * zero means unlimited
+       * 
+ * + * uint32 maxEntries = 3; + * @return The maxEntries. + */ + @java.lang.Override + public int getMaxEntries() { + return maxEntries_; + } + /** + *
+       * zero means unlimited
+       * 
+ * + * uint32 maxEntries = 3; + * @param value The maxEntries to set. + * @return This builder for chaining. + */ + public Builder setMaxEntries(int value) { + + maxEntries_ = value; + onChanged(); + return this; + } + /** + *
+       * zero means unlimited
+       * 
+ * + * uint32 maxEntries = 3; + * @return This builder for chaining. + */ + public Builder clearMaxEntries() { + + maxEntries_ = 0; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.GetAddressUtxosArg) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.GetAddressUtxosArg) + private static final cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg(); + } + + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public GetAddressUtxosArg parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GetAddressUtxosArg(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosArg getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface GetAddressUtxosReplyOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.GetAddressUtxosReply) + com.google.protobuf.MessageOrBuilder { + + /** + * string address = 6; + * @return The address. + */ + java.lang.String getAddress(); + /** + * string address = 6; + * @return The bytes for address. + */ + com.google.protobuf.ByteString + getAddressBytes(); + + /** + * bytes txid = 1; + * @return The txid. + */ + com.google.protobuf.ByteString getTxid(); + + /** + * int32 index = 2; + * @return The index. + */ + int getIndex(); + + /** + * bytes script = 3; + * @return The script. + */ + com.google.protobuf.ByteString getScript(); + + /** + * int64 valueZat = 4; + * @return The valueZat. + */ + long getValueZat(); + + /** + * uint64 height = 5; + * @return The height. + */ + long getHeight(); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.GetAddressUtxosReply} + */ + public static final class GetAddressUtxosReply extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.GetAddressUtxosReply) + GetAddressUtxosReplyOrBuilder { + private static final long serialVersionUID = 0L; + // Use GetAddressUtxosReply.newBuilder() to construct. + private GetAddressUtxosReply(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private GetAddressUtxosReply() { + address_ = ""; + txid_ = com.google.protobuf.ByteString.EMPTY; + script_ = com.google.protobuf.ByteString.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new GetAddressUtxosReply(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GetAddressUtxosReply( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + + txid_ = input.readBytes(); + break; + } + case 16: { + + index_ = input.readInt32(); + break; + } + case 26: { + + script_ = input.readBytes(); + break; + } + case 32: { + + valueZat_ = input.readInt64(); + break; + } + case 40: { + + height_ = input.readUInt64(); + break; + } + case 50: { + java.lang.String s = input.readStringRequireUtf8(); + + address_ = s; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.class, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder.class); + } + + public static final int ADDRESS_FIELD_NUMBER = 6; + private volatile java.lang.Object address_; + /** + * string address = 6; + * @return The address. + */ + @java.lang.Override + public java.lang.String getAddress() { + java.lang.Object ref = address_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + address_ = s; + return s; + } + } + /** + * string address = 6; + * @return The bytes for address. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getAddressBytes() { + java.lang.Object ref = address_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + address_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int TXID_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString txid_; + /** + * bytes txid = 1; + * @return The txid. + */ + @java.lang.Override + public com.google.protobuf.ByteString getTxid() { + return txid_; + } + + public static final int INDEX_FIELD_NUMBER = 2; + private int index_; + /** + * int32 index = 2; + * @return The index. + */ + @java.lang.Override + public int getIndex() { + return index_; + } + + public static final int SCRIPT_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString script_; + /** + * bytes script = 3; + * @return The script. + */ + @java.lang.Override + public com.google.protobuf.ByteString getScript() { + return script_; + } + + public static final int VALUEZAT_FIELD_NUMBER = 4; + private long valueZat_; + /** + * int64 valueZat = 4; + * @return The valueZat. + */ + @java.lang.Override + public long getValueZat() { + return valueZat_; + } + + public static final int HEIGHT_FIELD_NUMBER = 5; + private long height_; + /** + * uint64 height = 5; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!txid_.isEmpty()) { + output.writeBytes(1, txid_); + } + if (index_ != 0) { + output.writeInt32(2, index_); + } + if (!script_.isEmpty()) { + output.writeBytes(3, script_); + } + if (valueZat_ != 0L) { + output.writeInt64(4, valueZat_); + } + if (height_ != 0L) { + output.writeUInt64(5, height_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(address_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 6, address_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!txid_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, txid_); + } + if (index_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(2, index_); + } + if (!script_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, script_); + } + if (valueZat_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(4, valueZat_); + } + if (height_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(5, height_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(address_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(6, address_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply other = (cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply) obj; + + if (!getAddress() + .equals(other.getAddress())) return false; + if (!getTxid() + .equals(other.getTxid())) return false; + if (getIndex() + != other.getIndex()) return false; + if (!getScript() + .equals(other.getScript())) return false; + if (getValueZat() + != other.getValueZat()) return false; + if (getHeight() + != other.getHeight()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ADDRESS_FIELD_NUMBER; + hash = (53 * hash) + getAddress().hashCode(); + hash = (37 * hash) + TXID_FIELD_NUMBER; + hash = (53 * hash) + getTxid().hashCode(); + hash = (37 * hash) + INDEX_FIELD_NUMBER; + hash = (53 * hash) + getIndex(); + hash = (37 * hash) + SCRIPT_FIELD_NUMBER; + hash = (53 * hash) + getScript().hashCode(); + hash = (37 * hash) + VALUEZAT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getValueZat()); + hash = (37 * hash) + HEIGHT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getHeight()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.GetAddressUtxosReply} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.GetAddressUtxosReply) + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.class, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + address_ = ""; + + txid_ = com.google.protobuf.ByteString.EMPTY; + + index_ = 0; + + script_ = com.google.protobuf.ByteString.EMPTY; + + valueZat_ = 0L; + + height_ = 0L; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply build() { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply buildPartial() { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply result = new cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply(this); + result.address_ = address_; + result.txid_ = txid_; + result.index_ = index_; + result.script_ = script_; + result.valueZat_ = valueZat_; + result.height_ = height_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply other) { + if (other == cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.getDefaultInstance()) return this; + if (!other.getAddress().isEmpty()) { + address_ = other.address_; + onChanged(); + } + if (other.getTxid() != com.google.protobuf.ByteString.EMPTY) { + setTxid(other.getTxid()); + } + if (other.getIndex() != 0) { + setIndex(other.getIndex()); + } + if (other.getScript() != com.google.protobuf.ByteString.EMPTY) { + setScript(other.getScript()); + } + if (other.getValueZat() != 0L) { + setValueZat(other.getValueZat()); + } + if (other.getHeight() != 0L) { + setHeight(other.getHeight()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private java.lang.Object address_ = ""; + /** + * string address = 6; + * @return The address. + */ + public java.lang.String getAddress() { + java.lang.Object ref = address_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + address_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string address = 6; + * @return The bytes for address. + */ + public com.google.protobuf.ByteString + getAddressBytes() { + java.lang.Object ref = address_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + address_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string address = 6; + * @param value The address to set. + * @return This builder for chaining. + */ + public Builder setAddress( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + address_ = value; + onChanged(); + return this; + } + /** + * string address = 6; + * @return This builder for chaining. + */ + public Builder clearAddress() { + + address_ = getDefaultInstance().getAddress(); + onChanged(); + return this; + } + /** + * string address = 6; + * @param value The bytes for address to set. + * @return This builder for chaining. + */ + public Builder setAddressBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + address_ = value; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString txid_ = com.google.protobuf.ByteString.EMPTY; + /** + * bytes txid = 1; + * @return The txid. + */ + @java.lang.Override + public com.google.protobuf.ByteString getTxid() { + return txid_; + } + /** + * bytes txid = 1; + * @param value The txid to set. + * @return This builder for chaining. + */ + public Builder setTxid(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + txid_ = value; + onChanged(); + return this; + } + /** + * bytes txid = 1; + * @return This builder for chaining. + */ + public Builder clearTxid() { + + txid_ = getDefaultInstance().getTxid(); + onChanged(); + return this; + } + + private int index_ ; + /** + * int32 index = 2; + * @return The index. + */ + @java.lang.Override + public int getIndex() { + return index_; + } + /** + * int32 index = 2; + * @param value The index to set. + * @return This builder for chaining. + */ + public Builder setIndex(int value) { + + index_ = value; + onChanged(); + return this; + } + /** + * int32 index = 2; + * @return This builder for chaining. + */ + public Builder clearIndex() { + + index_ = 0; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString script_ = com.google.protobuf.ByteString.EMPTY; + /** + * bytes script = 3; + * @return The script. + */ + @java.lang.Override + public com.google.protobuf.ByteString getScript() { + return script_; + } + /** + * bytes script = 3; + * @param value The script to set. + * @return This builder for chaining. + */ + public Builder setScript(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + script_ = value; + onChanged(); + return this; + } + /** + * bytes script = 3; + * @return This builder for chaining. + */ + public Builder clearScript() { + + script_ = getDefaultInstance().getScript(); + onChanged(); + return this; + } + + private long valueZat_ ; + /** + * int64 valueZat = 4; + * @return The valueZat. + */ + @java.lang.Override + public long getValueZat() { + return valueZat_; + } + /** + * int64 valueZat = 4; + * @param value The valueZat to set. + * @return This builder for chaining. + */ + public Builder setValueZat(long value) { + + valueZat_ = value; + onChanged(); + return this; + } + /** + * int64 valueZat = 4; + * @return This builder for chaining. + */ + public Builder clearValueZat() { + + valueZat_ = 0L; + onChanged(); + return this; + } + + private long height_ ; + /** + * uint64 height = 5; + * @return The height. + */ + @java.lang.Override + public long getHeight() { + return height_; + } + /** + * uint64 height = 5; + * @param value The height to set. + * @return This builder for chaining. + */ + public Builder setHeight(long value) { + + height_ = value; + onChanged(); + return this; + } + /** + * uint64 height = 5; + * @return This builder for chaining. + */ + public Builder clearHeight() { + + height_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.GetAddressUtxosReply) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.GetAddressUtxosReply) + private static final cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply(); + } + + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public GetAddressUtxosReply parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GetAddressUtxosReply(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface GetAddressUtxosReplyListOrBuilder extends + // @@protoc_insertion_point(interface_extends:cash.z.wallet.sdk.rpc.GetAddressUtxosReplyList) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + java.util.List + getAddressUtxosList(); + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply getAddressUtxos(int index); + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + int getAddressUtxosCount(); + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + java.util.List + getAddressUtxosOrBuilderList(); + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyOrBuilder getAddressUtxosOrBuilder( + int index); + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.GetAddressUtxosReplyList} + */ + public static final class GetAddressUtxosReplyList extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:cash.z.wallet.sdk.rpc.GetAddressUtxosReplyList) + GetAddressUtxosReplyListOrBuilder { + private static final long serialVersionUID = 0L; + // Use GetAddressUtxosReplyList.newBuilder() to construct. + private GetAddressUtxosReplyList(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private GetAddressUtxosReplyList() { + addressUtxos_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new GetAddressUtxosReplyList(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GetAddressUtxosReplyList( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + addressUtxos_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + addressUtxos_.add( + input.readMessage(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + addressUtxos_ = java.util.Collections.unmodifiableList(addressUtxos_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.class, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.Builder.class); + } + + public static final int ADDRESSUTXOS_FIELD_NUMBER = 1; + private java.util.List addressUtxos_; + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + @java.lang.Override + public java.util.List getAddressUtxosList() { + return addressUtxos_; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + @java.lang.Override + public java.util.List + getAddressUtxosOrBuilderList() { + return addressUtxos_; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + @java.lang.Override + public int getAddressUtxosCount() { + return addressUtxos_.size(); + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply getAddressUtxos(int index) { + return addressUtxos_.get(index); + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyOrBuilder getAddressUtxosOrBuilder( + int index) { + return addressUtxos_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < addressUtxos_.size(); i++) { + output.writeMessage(1, addressUtxos_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (int i = 0; i < addressUtxos_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, addressUtxos_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList)) { + return super.equals(obj); + } + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList other = (cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList) obj; + + if (!getAddressUtxosList() + .equals(other.getAddressUtxosList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getAddressUtxosCount() > 0) { + hash = (37 * hash) + ADDRESSUTXOS_FIELD_NUMBER; + hash = (53 * hash) + getAddressUtxosList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code cash.z.wallet.sdk.rpc.GetAddressUtxosReplyList} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:cash.z.wallet.sdk.rpc.GetAddressUtxosReplyList) + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyListOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_fieldAccessorTable + .ensureFieldAccessorsInitialized( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.class, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.Builder.class); + } + + // Construct using cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getAddressUtxosFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (addressUtxosBuilder_ == null) { + addressUtxos_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + addressUtxosBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return cash.z.wallet.sdk.rpc.Service.internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_descriptor; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList getDefaultInstanceForType() { + return cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.getDefaultInstance(); + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList build() { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList buildPartial() { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList result = new cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList(this); + int from_bitField0_ = bitField0_; + if (addressUtxosBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + addressUtxos_ = java.util.Collections.unmodifiableList(addressUtxos_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.addressUtxos_ = addressUtxos_; + } else { + result.addressUtxos_ = addressUtxosBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList) { + return mergeFrom((cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList other) { + if (other == cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList.getDefaultInstance()) return this; + if (addressUtxosBuilder_ == null) { + if (!other.addressUtxos_.isEmpty()) { + if (addressUtxos_.isEmpty()) { + addressUtxos_ = other.addressUtxos_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAddressUtxosIsMutable(); + addressUtxos_.addAll(other.addressUtxos_); + } + onChanged(); + } + } else { + if (!other.addressUtxos_.isEmpty()) { + if (addressUtxosBuilder_.isEmpty()) { + addressUtxosBuilder_.dispose(); + addressUtxosBuilder_ = null; + addressUtxos_ = other.addressUtxos_; + bitField0_ = (bitField0_ & ~0x00000001); + addressUtxosBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getAddressUtxosFieldBuilder() : null; + } else { + addressUtxosBuilder_.addAllMessages(other.addressUtxos_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.util.List addressUtxos_ = + java.util.Collections.emptyList(); + private void ensureAddressUtxosIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + addressUtxos_ = new java.util.ArrayList(addressUtxos_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyOrBuilder> addressUtxosBuilder_; + + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public java.util.List getAddressUtxosList() { + if (addressUtxosBuilder_ == null) { + return java.util.Collections.unmodifiableList(addressUtxos_); + } else { + return addressUtxosBuilder_.getMessageList(); + } + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public int getAddressUtxosCount() { + if (addressUtxosBuilder_ == null) { + return addressUtxos_.size(); + } else { + return addressUtxosBuilder_.getCount(); + } + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply getAddressUtxos(int index) { + if (addressUtxosBuilder_ == null) { + return addressUtxos_.get(index); + } else { + return addressUtxosBuilder_.getMessage(index); + } + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder setAddressUtxos( + int index, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply value) { + if (addressUtxosBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAddressUtxosIsMutable(); + addressUtxos_.set(index, value); + onChanged(); + } else { + addressUtxosBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder setAddressUtxos( + int index, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder builderForValue) { + if (addressUtxosBuilder_ == null) { + ensureAddressUtxosIsMutable(); + addressUtxos_.set(index, builderForValue.build()); + onChanged(); + } else { + addressUtxosBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder addAddressUtxos(cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply value) { + if (addressUtxosBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAddressUtxosIsMutable(); + addressUtxos_.add(value); + onChanged(); + } else { + addressUtxosBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder addAddressUtxos( + int index, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply value) { + if (addressUtxosBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAddressUtxosIsMutable(); + addressUtxos_.add(index, value); + onChanged(); + } else { + addressUtxosBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder addAddressUtxos( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder builderForValue) { + if (addressUtxosBuilder_ == null) { + ensureAddressUtxosIsMutable(); + addressUtxos_.add(builderForValue.build()); + onChanged(); + } else { + addressUtxosBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder addAddressUtxos( + int index, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder builderForValue) { + if (addressUtxosBuilder_ == null) { + ensureAddressUtxosIsMutable(); + addressUtxos_.add(index, builderForValue.build()); + onChanged(); + } else { + addressUtxosBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder addAllAddressUtxos( + java.lang.Iterable values) { + if (addressUtxosBuilder_ == null) { + ensureAddressUtxosIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, addressUtxos_); + onChanged(); + } else { + addressUtxosBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder clearAddressUtxos() { + if (addressUtxosBuilder_ == null) { + addressUtxos_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + addressUtxosBuilder_.clear(); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public Builder removeAddressUtxos(int index) { + if (addressUtxosBuilder_ == null) { + ensureAddressUtxosIsMutable(); + addressUtxos_.remove(index); + onChanged(); + } else { + addressUtxosBuilder_.remove(index); + } + return this; + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder getAddressUtxosBuilder( + int index) { + return getAddressUtxosFieldBuilder().getBuilder(index); + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyOrBuilder getAddressUtxosOrBuilder( + int index) { + if (addressUtxosBuilder_ == null) { + return addressUtxos_.get(index); } else { + return addressUtxosBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public java.util.List + getAddressUtxosOrBuilderList() { + if (addressUtxosBuilder_ != null) { + return addressUtxosBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(addressUtxos_); + } + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder addAddressUtxosBuilder() { + return getAddressUtxosFieldBuilder().addBuilder( + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.getDefaultInstance()); + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder addAddressUtxosBuilder( + int index) { + return getAddressUtxosFieldBuilder().addBuilder( + index, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.getDefaultInstance()); + } + /** + * repeated .cash.z.wallet.sdk.rpc.GetAddressUtxosReply addressUtxos = 1; + */ + public java.util.List + getAddressUtxosBuilderList() { + return getAddressUtxosFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyOrBuilder> + getAddressUtxosFieldBuilder() { + if (addressUtxosBuilder_ == null) { + addressUtxosBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReply.Builder, cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyOrBuilder>( + addressUtxos_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + addressUtxos_ = null; + } + return addressUtxosBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:cash.z.wallet.sdk.rpc.GetAddressUtxosReplyList) + } + + // @@protoc_insertion_point(class_scope:cash.z.wallet.sdk.rpc.GetAddressUtxosReplyList) + private static final cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList(); + } + + public static cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public GetAddressUtxosReplyList parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GetAddressUtxosReplyList(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public cash.z.wallet.sdk.rpc.Service.GetAddressUtxosReplyList getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_BlockID_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_BlockID_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_BlockRange_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_BlockRange_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_TxFilter_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_TxFilter_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_RawTransaction_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_RawTransaction_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_SendResponse_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_SendResponse_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_ChainSpec_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_ChainSpec_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_Empty_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_Empty_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_LightdInfo_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_LightdInfo_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_Duration_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_Duration_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_PingResponse_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_PingResponse_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_Address_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_Address_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_AddressList_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_AddressList_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_Balance_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_Balance_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_Exclude_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_Exclude_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_TreeState_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_TreeState_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\rservice.proto\022\025cash.z.wallet.sdk.rpc\032\025" + + "compact_formats.proto\"\'\n\007BlockID\022\016\n\006heig" + + "ht\030\001 \001(\004\022\014\n\004hash\030\002 \001(\014\"h\n\nBlockRange\022-\n\005" + + "start\030\001 \001(\0132\036.cash.z.wallet.sdk.rpc.Bloc" + + "kID\022+\n\003end\030\002 \001(\0132\036.cash.z.wallet.sdk.rpc" + + ".BlockID\"V\n\010TxFilter\022-\n\005block\030\001 \001(\0132\036.ca" + + "sh.z.wallet.sdk.rpc.BlockID\022\r\n\005index\030\002 \001" + + "(\004\022\014\n\004hash\030\003 \001(\014\".\n\016RawTransaction\022\014\n\004da" + + "ta\030\001 \001(\014\022\016\n\006height\030\002 \001(\004\"7\n\014SendResponse" + + "\022\021\n\terrorCode\030\001 \001(\005\022\024\n\014errorMessage\030\002 \001(" + + "\t\"\013\n\tChainSpec\"\007\n\005Empty\"\272\002\n\nLightdInfo\022\017" + + "\n\007version\030\001 \001(\t\022\016\n\006vendor\030\002 \001(\t\022\024\n\014taddr" + + "Support\030\003 \001(\010\022\021\n\tchainName\030\004 \001(\t\022\037\n\027sapl" + + "ingActivationHeight\030\005 \001(\004\022\031\n\021consensusBr" + + "anchId\030\006 \001(\t\022\023\n\013blockHeight\030\007 \001(\004\022\021\n\tgit" + + "Commit\030\010 \001(\t\022\016\n\006branch\030\t \001(\t\022\021\n\tbuildDat" + + "e\030\n \001(\t\022\021\n\tbuildUser\030\013 \001(\t\022\027\n\017estimatedH" + + "eight\030\014 \001(\004\022\024\n\014piratedBuild\030\r \001(\t\022\031\n\021pir" + + "atedSubversion\030\016 \001(\t\"b\n\035TransparentAddre" + + "ssBlockFilter\022\017\n\007address\030\001 \001(\t\0220\n\005range\030" + + "\002 \001(\0132!.cash.z.wallet.sdk.rpc.BlockRange" + + "\"\036\n\010Duration\022\022\n\nintervalUs\030\001 \001(\003\"+\n\014Ping" + + "Response\022\r\n\005entry\030\001 \001(\003\022\014\n\004exit\030\002 \001(\003\"\032\n" + + "\007Address\022\017\n\007address\030\001 \001(\t\" \n\013AddressList" + + "\022\021\n\taddresses\030\001 \003(\t\"\033\n\007Balance\022\020\n\010valueZ" + + "at\030\001 \001(\003\"\027\n\007Exclude\022\014\n\004txid\030\001 \003(\014\"V\n\tTre" + + "eState\022\017\n\007network\030\001 \001(\t\022\016\n\006height\030\002 \001(\004\022" + + "\014\n\004hash\030\003 \001(\t\022\014\n\004time\030\004 \001(\r\022\014\n\004tree\030\005 \001(" + + "\t\"P\n\022GetAddressUtxosArg\022\021\n\taddresses\030\001 \003" + + "(\t\022\023\n\013startHeight\030\002 \001(\004\022\022\n\nmaxEntries\030\003 " + + "\001(\r\"v\n\024GetAddressUtxosReply\022\017\n\007address\030\006" + + " \001(\t\022\014\n\004txid\030\001 \001(\014\022\r\n\005index\030\002 \001(\005\022\016\n\006scr" + + "ipt\030\003 \001(\014\022\020\n\010valueZat\030\004 \001(\003\022\016\n\006height\030\005 " + + "\001(\004\"]\n\030GetAddressUtxosReplyList\022A\n\014addre" + + "ssUtxos\030\001 \003(\0132+.cash.z.wallet.sdk.rpc.Ge" + + "tAddressUtxosReply2\273\n\n\021CompactTxStreamer" + + "\022T\n\016GetLatestBlock\022 .cash.z.wallet.sdk.r" + + "pc.ChainSpec\032\036.cash.z.wallet.sdk.rpc.Blo" + + "ckID\"\000\022Q\n\010GetBlock\022\036.cash.z.wallet.sdk.r" + + "pc.BlockID\032#.cash.z.wallet.sdk.rpc.Compa" + + "ctBlock\"\000\022[\n\rGetBlockRange\022!.cash.z.wall" + + "et.sdk.rpc.BlockRange\032#.cash.z.wallet.sd" + + "k.rpc.CompactBlock\"\0000\001\022Z\n\016GetTransaction" + + "\022\037.cash.z.wallet.sdk.rpc.TxFilter\032%.cash" + + ".z.wallet.sdk.rpc.RawTransaction\"\000\022_\n\017Se" + + "ndTransaction\022%.cash.z.wallet.sdk.rpc.Ra" + + "wTransaction\032#.cash.z.wallet.sdk.rpc.Sen" + + "dResponse\"\000\022s\n\020GetTaddressTxids\0224.cash.z" + + ".wallet.sdk.rpc.TransparentAddressBlockF" + + "ilter\032%.cash.z.wallet.sdk.rpc.RawTransac" + + "tion\"\0000\001\022Z\n\022GetTaddressBalance\022\".cash.z." + + "wallet.sdk.rpc.AddressList\032\036.cash.z.wall" + + "et.sdk.rpc.Balance\"\000\022^\n\030GetTaddressBalan" + + "ceStream\022\036.cash.z.wallet.sdk.rpc.Address" + + "\032\036.cash.z.wallet.sdk.rpc.Balance\"\000(\001\022T\n\014" + + "GetMempoolTx\022\036.cash.z.wallet.sdk.rpc.Exc" + + "lude\032 .cash.z.wallet.sdk.rpc.CompactTx\"\000" + + "0\001\022R\n\014GetTreeState\022\036.cash.z.wallet.sdk.r" + + "pc.BlockID\032 .cash.z.wallet.sdk.rpc.TreeS" + + "tate\"\000\022o\n\017GetAddressUtxos\022).cash.z.walle" + + "t.sdk.rpc.GetAddressUtxosArg\032/.cash.z.wa" + + "llet.sdk.rpc.GetAddressUtxosReplyList\"\000\022" + + "s\n\025GetAddressUtxosStream\022).cash.z.wallet" + + ".sdk.rpc.GetAddressUtxosArg\032+.cash.z.wal" + + "let.sdk.rpc.GetAddressUtxosReply\"\0000\001\022R\n\r" + + "GetLightdInfo\022\034.cash.z.wallet.sdk.rpc.Em" + + "pty\032!.cash.z.wallet.sdk.rpc.LightdInfo\"\000" + + "\022N\n\004Ping\022\037.cash.z.wallet.sdk.rpc.Duratio" + + "n\032#.cash.z.wallet.sdk.rpc.PingResponse\"\000" + + "B\033Z\026lightwalletd/walletrpc\272\002\000b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + cash.z.wallet.sdk.rpc.CompactFormats.getDescriptor(), + }); + internal_static_cash_z_wallet_sdk_rpc_BlockID_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_cash_z_wallet_sdk_rpc_BlockID_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_BlockID_descriptor, + new java.lang.String[] { "Height", "Hash", }); + internal_static_cash_z_wallet_sdk_rpc_BlockRange_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_cash_z_wallet_sdk_rpc_BlockRange_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_BlockRange_descriptor, + new java.lang.String[] { "Start", "End", }); + internal_static_cash_z_wallet_sdk_rpc_TxFilter_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_cash_z_wallet_sdk_rpc_TxFilter_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_TxFilter_descriptor, + new java.lang.String[] { "Block", "Index", "Hash", }); + internal_static_cash_z_wallet_sdk_rpc_RawTransaction_descriptor = + getDescriptor().getMessageTypes().get(3); + internal_static_cash_z_wallet_sdk_rpc_RawTransaction_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_RawTransaction_descriptor, + new java.lang.String[] { "Data", "Height", }); + internal_static_cash_z_wallet_sdk_rpc_SendResponse_descriptor = + getDescriptor().getMessageTypes().get(4); + internal_static_cash_z_wallet_sdk_rpc_SendResponse_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_SendResponse_descriptor, + new java.lang.String[] { "ErrorCode", "ErrorMessage", }); + internal_static_cash_z_wallet_sdk_rpc_ChainSpec_descriptor = + getDescriptor().getMessageTypes().get(5); + internal_static_cash_z_wallet_sdk_rpc_ChainSpec_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_ChainSpec_descriptor, + new java.lang.String[] { }); + internal_static_cash_z_wallet_sdk_rpc_Empty_descriptor = + getDescriptor().getMessageTypes().get(6); + internal_static_cash_z_wallet_sdk_rpc_Empty_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_Empty_descriptor, + new java.lang.String[] { }); + internal_static_cash_z_wallet_sdk_rpc_LightdInfo_descriptor = + getDescriptor().getMessageTypes().get(7); + internal_static_cash_z_wallet_sdk_rpc_LightdInfo_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_LightdInfo_descriptor, + new java.lang.String[] { "Version", "Vendor", "TaddrSupport", "ChainName", "SaplingActivationHeight", "ConsensusBranchId", "BlockHeight", "GitCommit", "Branch", "BuildDate", "BuildUser", "EstimatedHeight", "PiratedBuild", "PiratedSubversion", }); + internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_descriptor = + getDescriptor().getMessageTypes().get(8); + internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_TransparentAddressBlockFilter_descriptor, + new java.lang.String[] { "Address", "Range", }); + internal_static_cash_z_wallet_sdk_rpc_Duration_descriptor = + getDescriptor().getMessageTypes().get(9); + internal_static_cash_z_wallet_sdk_rpc_Duration_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_Duration_descriptor, + new java.lang.String[] { "IntervalUs", }); + internal_static_cash_z_wallet_sdk_rpc_PingResponse_descriptor = + getDescriptor().getMessageTypes().get(10); + internal_static_cash_z_wallet_sdk_rpc_PingResponse_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_PingResponse_descriptor, + new java.lang.String[] { "Entry", "Exit", }); + internal_static_cash_z_wallet_sdk_rpc_Address_descriptor = + getDescriptor().getMessageTypes().get(11); + internal_static_cash_z_wallet_sdk_rpc_Address_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_Address_descriptor, + new java.lang.String[] { "Address", }); + internal_static_cash_z_wallet_sdk_rpc_AddressList_descriptor = + getDescriptor().getMessageTypes().get(12); + internal_static_cash_z_wallet_sdk_rpc_AddressList_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_AddressList_descriptor, + new java.lang.String[] { "Addresses", }); + internal_static_cash_z_wallet_sdk_rpc_Balance_descriptor = + getDescriptor().getMessageTypes().get(13); + internal_static_cash_z_wallet_sdk_rpc_Balance_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_Balance_descriptor, + new java.lang.String[] { "ValueZat", }); + internal_static_cash_z_wallet_sdk_rpc_Exclude_descriptor = + getDescriptor().getMessageTypes().get(14); + internal_static_cash_z_wallet_sdk_rpc_Exclude_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_Exclude_descriptor, + new java.lang.String[] { "Txid", }); + internal_static_cash_z_wallet_sdk_rpc_TreeState_descriptor = + getDescriptor().getMessageTypes().get(15); + internal_static_cash_z_wallet_sdk_rpc_TreeState_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_TreeState_descriptor, + new java.lang.String[] { "Network", "Height", "Hash", "Time", "Tree", }); + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_descriptor = + getDescriptor().getMessageTypes().get(16); + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosArg_descriptor, + new java.lang.String[] { "Addresses", "StartHeight", "MaxEntries", }); + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_descriptor = + getDescriptor().getMessageTypes().get(17); + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReply_descriptor, + new java.lang.String[] { "Address", "Txid", "Index", "Script", "ValueZat", "Height", }); + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_descriptor = + getDescriptor().getMessageTypes().get(18); + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_cash_z_wallet_sdk_rpc_GetAddressUtxosReplyList_descriptor, + new java.lang.String[] { "AddressUtxos", }); + cash.z.wallet.sdk.rpc.CompactFormats.getDescriptor(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/src/main/java/com/rust/litewalletjni/LiteWalletJni.java b/src/main/java/com/rust/litewalletjni/LiteWalletJni.java new file mode 100644 index 00000000..3f1a506d --- /dev/null +++ b/src/main/java/com/rust/litewalletjni/LiteWalletJni.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * LiteWalletJni code based on https://github.com/PirateNetwork/cordova-plugin-litewallet + * + * MIT License + * + * Copyright (c) 2020 Zero Currency Coin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.rust.litewalletjni; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.controller.PirateChainWalletController; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class LiteWalletJni { + + protected static final Logger LOGGER = LogManager.getLogger(LiteWalletJni.class); + + public static native String initlogging(); + public static native String initnew(final String serveruri, final String params, final String saplingOutputb64, final String saplingSpendb64); + public static native String initfromseed(final String serveruri, final String params, final String seed, final String birthday, final String saplingOutputb64, final String saplingSpendb64); + public static native String initfromb64(final String serveruri, final String params, final String datab64, final String saplingOutputb64, final String saplingSpendb64); + public static native String save(); + + public static native String execute(final String cmd, final String args); + public static native String getseedphrase(); + public static native String getseedphrasefromentropyb64(final String entropy64); + public static native String checkseedphrase(final String input); + + + private static boolean loaded = false; + + public static void loadLibrary() { + if (loaded) { + return; + } + String osName = System.getProperty("os.name"); + String osArchitecture = System.getProperty("os.arch"); + + LOGGER.info("OS Name: {}", osName); + LOGGER.info("OS Architecture: {}", osArchitecture); + + try { + String libFileName = PirateChainWalletController.getRustLibFilename(); + if (libFileName == null) { + LOGGER.info("Library not found for OS: {}, arch: {}", osName, osArchitecture); + return; + } + + Path libPath = Paths.get(PirateChainWalletController.getRustLibOuterDirectory().toString(), libFileName); + System.load(libPath.toAbsolutePath().toString()); + loaded = true; + } + catch (UnsatisfiedLinkError e) { + LOGGER.info("Unable to load library"); + } + } + + public static boolean isLoaded() { + return loaded; + } + +} diff --git a/src/main/java/org/qortal/ApplyUpdate.java b/src/main/java/org/qortal/ApplyUpdate.java index 796bf580..7a870460 100644 --- a/src/main/java/org/qortal/ApplyUpdate.java +++ b/src/main/java/org/qortal/ApplyUpdate.java @@ -8,6 +8,7 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.security.Security; import java.util.*; +import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -18,6 +19,8 @@ import org.qortal.api.ApiRequest; import org.qortal.controller.AutoUpdate; import org.qortal.settings.Settings; +import static org.qortal.controller.AutoUpdate.AGENTLIB_JVM_HOLDER_ARG; + public class ApplyUpdate { static { @@ -197,6 +200,11 @@ public class ApplyUpdate { // JVM arguments javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); + // Reapply any retained, but disabled, -agentlib JVM arg + javaCmd = javaCmd.stream() + .map(arg -> arg.replace(AGENTLIB_JVM_HOLDER_ARG, "-agentlib")) + .collect(Collectors.toList()); + // Call mainClass in JAR javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME)); @@ -205,7 +213,7 @@ public class ApplyUpdate { } try { - LOGGER.info(() -> String.format("Restarting node with: %s", String.join(" ", javaCmd))); + LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd))); ProcessBuilder processBuilder = new ProcessBuilder(javaCmd); @@ -214,8 +222,15 @@ public class ApplyUpdate { processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE); } - processBuilder.start(); - } catch (IOException e) { + // New process will inherit our stdout and stderr + processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT); + processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT); + + Process process = processBuilder.start(); + + // Nothing to pipe to new process, so close output stream (process's stdin) + process.getOutputStream().close(); + } catch (Exception e) { LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage())); } } diff --git a/src/main/java/org/qortal/account/Account.java b/src/main/java/org/qortal/account/Account.java index c3a25fb6..2c75dbc0 100644 --- a/src/main/java/org/qortal/account/Account.java +++ b/src/main/java/org/qortal/account/Account.java @@ -211,7 +211,8 @@ public class Account { if (level != null && level >= BlockChain.getInstance().getMinAccountLevelToMint()) return true; - if (Account.isFounder(accountData.getFlags())) + // Founders can always mint, unless they have a penalty + if (Account.isFounder(accountData.getFlags()) && accountData.getBlocksMintedPenalty() == 0) return true; return false; @@ -222,6 +223,11 @@ public class Account { return this.repository.getAccountRepository().getMintedBlockCount(this.address); } + /** Returns account's blockMintedPenalty or null if account not found in repository. */ + public Integer getBlocksMintedPenalty() throws DataException { + return this.repository.getAccountRepository().getBlocksMintedPenaltyCount(this.address); + } + /** Returns whether account can build reward-shares. *

@@ -243,7 +249,7 @@ public class Account { if (level != null && level >= BlockChain.getInstance().getMinAccountLevelToRewardShare()) return true; - if (Account.isFounder(accountData.getFlags())) + if (Account.isFounder(accountData.getFlags()) && accountData.getBlocksMintedPenalty() == 0) return true; return false; @@ -271,7 +277,7 @@ public class Account { /** * Returns 'effective' minting level, or zero if account does not exist/cannot mint. *

- * For founder accounts, this returns "founderEffectiveMintingLevel" from blockchain config. + * For founder accounts with no penalty, this returns "founderEffectiveMintingLevel" from blockchain config. * * @return 0+ * @throws DataException @@ -281,7 +287,8 @@ public class Account { if (accountData == null) return 0; - if (Account.isFounder(accountData.getFlags())) + // Founders are assigned a different effective minting level, as long as they have no penalty + if (Account.isFounder(accountData.getFlags()) && accountData.getBlocksMintedPenalty() == 0) return BlockChain.getInstance().getFounderEffectiveMintingLevel(); return accountData.getLevel(); @@ -289,8 +296,6 @@ public class Account { /** * Returns 'effective' minting level, or zero if reward-share does not exist. - *

- * this is being used on src/main/java/org/qortal/api/resource/AddressesResource.java to fulfil the online accounts api call * * @param repository * @param rewardSharePublicKey @@ -309,7 +314,7 @@ public class Account { /** * Returns 'effective' minting level, with a fix for the zero level. *

- * For founder accounts, this returns "founderEffectiveMintingLevel" from blockchain config. + * For founder accounts with no penalty, this returns "founderEffectiveMintingLevel" from blockchain config. * * @param repository * @param rewardSharePublicKey @@ -322,7 +327,7 @@ public class Account { if (rewardShareData == null) return 0; - else if(!rewardShareData.getMinter().equals(rewardShareData.getRecipient()))//the minter is different than the recipient this means sponsorship + else if (!rewardShareData.getMinter().equals(rewardShareData.getRecipient())) // Sponsorship reward share return 0; Account rewardShareMinter = new Account(repository, rewardShareData.getMinter()); diff --git a/src/main/java/org/qortal/account/PrivateKeyAccount.java b/src/main/java/org/qortal/account/PrivateKeyAccount.java index 3b370d12..4b646b4a 100644 --- a/src/main/java/org/qortal/account/PrivateKeyAccount.java +++ b/src/main/java/org/qortal/account/PrivateKeyAccount.java @@ -11,15 +11,15 @@ public class PrivateKeyAccount extends PublicKeyAccount { private final Ed25519PrivateKeyParameters edPrivateKeyParams; /** - * Create PrivateKeyAccount using byte[32] seed. + * Create PrivateKeyAccount using byte[32] private key. * - * @param seed + * @param privateKey * byte[32] used to create private/public key pair * @throws IllegalArgumentException - * if passed invalid seed + * if passed invalid privateKey */ - public PrivateKeyAccount(Repository repository, byte[] seed) { - this(repository, new Ed25519PrivateKeyParameters(seed, 0)); + public PrivateKeyAccount(Repository repository, byte[] privateKey) { + this(repository, new Ed25519PrivateKeyParameters(privateKey, 0)); } private PrivateKeyAccount(Repository repository, Ed25519PrivateKeyParameters edPrivateKeyParams) { @@ -37,10 +37,6 @@ public class PrivateKeyAccount extends PublicKeyAccount { return this.privateKey; } - public static byte[] toPublicKey(byte[] seed) { - return new Ed25519PrivateKeyParameters(seed, 0).generatePublicKey().getEncoded(); - } - public byte[] sign(byte[] message) { return Crypto.sign(this.edPrivateKeyParams, message); } diff --git a/src/main/java/org/qortal/account/SelfSponsorshipAlgoV1.java b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV1.java new file mode 100644 index 00000000..725e53f5 --- /dev/null +++ b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV1.java @@ -0,0 +1,367 @@ +package org.qortal.account; + +import org.qortal.api.resource.TransactionsResource; +import org.qortal.asset.Asset; +import org.qortal.data.account.AccountData; +import org.qortal.data.naming.NameData; +import org.qortal.data.transaction.*; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.transaction.Transaction.TransactionType; + +import java.util.*; +import java.util.stream.Collectors; + +public class SelfSponsorshipAlgoV1 { + + private final Repository repository; + private final String address; + private final AccountData accountData; + private final long snapshotTimestamp; + private final boolean override; + + private int registeredNameCount = 0; + private int suspiciousCount = 0; + private int suspiciousPercent = 0; + private int consolidationCount = 0; + private int bulkIssuanceCount = 0; + private int recentSponsorshipCount = 0; + + private List sponsorshipRewardShares = new ArrayList<>(); + private final Map> paymentsByAddress = new HashMap<>(); + private final Set sponsees = new LinkedHashSet<>(); + private Set consolidatedAddresses = new LinkedHashSet<>(); + private final Set zeroTransactionAddreses = new LinkedHashSet<>(); + private final Set penaltyAddresses = new LinkedHashSet<>(); + + public SelfSponsorshipAlgoV1(Repository repository, String address, long snapshotTimestamp, boolean override) throws DataException { + this.repository = repository; + this.address = address; + this.accountData = this.repository.getAccountRepository().getAccount(this.address); + this.snapshotTimestamp = snapshotTimestamp; + this.override = override; + } + + public String getAddress() { + return this.address; + } + + public Set getPenaltyAddresses() { + return this.penaltyAddresses; + } + + + public void run() throws DataException { + if (this.accountData == null) { + // Nothing to do + return; + } + + this.fetchSponsorshipRewardShares(); + if (this.sponsorshipRewardShares.isEmpty()) { + // Nothing to do + return; + } + + this.findConsolidatedRewards(); + this.findBulkIssuance(); + this.findRegisteredNameCount(); + this.findRecentSponsorshipCount(); + + int score = this.calculateScore(); + if (score <= 0 && !override) { + return; + } + + String newAddress = this.getDestinationAccount(this.address); + while (newAddress != null) { + // Found destination account + this.penaltyAddresses.add(newAddress); + + // Run algo for this address, but in "override" mode because it has already been flagged + SelfSponsorshipAlgoV1 algoV1 = new SelfSponsorshipAlgoV1(this.repository, newAddress, this.snapshotTimestamp, true); + algoV1.run(); + this.penaltyAddresses.addAll(algoV1.getPenaltyAddresses()); + + newAddress = this.getDestinationAccount(newAddress); + } + + this.penaltyAddresses.add(this.address); + + if (this.override || this.recentSponsorshipCount < 20) { + this.penaltyAddresses.addAll(this.consolidatedAddresses); + this.penaltyAddresses.addAll(this.zeroTransactionAddreses); + } + else { + this.penaltyAddresses.addAll(this.sponsees); + } + } + + private String getDestinationAccount(String address) throws DataException { + List transferPrivsTransactions = fetchTransferPrivsForAddress(address); + if (transferPrivsTransactions.isEmpty()) { + // No TRANSFER_PRIVS transactions for this address + return null; + } + + AccountData accountData = this.repository.getAccountRepository().getAccount(address); + if (accountData == null) { + return null; + } + + for (TransactionData transactionData : transferPrivsTransactions) { + TransferPrivsTransactionData transferPrivsTransactionData = (TransferPrivsTransactionData) transactionData; + if (Arrays.equals(transferPrivsTransactionData.getSenderPublicKey(), accountData.getPublicKey())) { + return transferPrivsTransactionData.getRecipient(); + } + } + + return null; + } + + private void findConsolidatedRewards() throws DataException { + List sponseesThatSentRewards = new ArrayList<>(); + Map paymentRecipients = new HashMap<>(); + + // Collect outgoing payments of each sponsee + for (String sponseeAddress : this.sponsees) { + + // Firstly fetch all payments for address, since the functions below depend on this data + this.fetchPaymentsForAddress(sponseeAddress); + + // Check if the address has zero relevant transactions + if (this.hasZeroTransactions(sponseeAddress)) { + this.zeroTransactionAddreses.add(sponseeAddress); + } + + // Get payment recipients + List allPaymentRecipients = this.fetchOutgoingPaymentRecipientsForAddress(sponseeAddress); + if (allPaymentRecipients.isEmpty()) { + continue; + } + sponseesThatSentRewards.add(sponseeAddress); + + List addressesPaidByThisSponsee = new ArrayList<>(); + for (String paymentRecipient : allPaymentRecipients) { + if (addressesPaidByThisSponsee.contains(paymentRecipient)) { + // We already tracked this association - don't allow multiple to stack up + continue; + } + addressesPaidByThisSponsee.add(paymentRecipient); + + // Increment count for this recipient, or initialize to 1 if not present + if (paymentRecipients.computeIfPresent(paymentRecipient, (k, v) -> v + 1) == null) { + paymentRecipients.put(paymentRecipient, 1); + } + } + + } + + // Exclude addresses with a low number of payments + Map filteredPaymentRecipients = paymentRecipients.entrySet().stream() + .filter(p -> p.getValue() != null && p.getValue() >= 10) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // Now check how many sponsees have sent to this subset of addresses + Map sponseesThatConsolidatedRewards = new HashMap<>(); + for (String sponseeAddress : sponseesThatSentRewards) { + List allPaymentRecipients = this.fetchOutgoingPaymentRecipientsForAddress(sponseeAddress); + // Remove any that aren't to one of the flagged recipients (i.e. consolidation) + allPaymentRecipients.removeIf(r -> !filteredPaymentRecipients.containsKey(r)); + + int count = allPaymentRecipients.size(); + if (count == 0) { + continue; + } + if (sponseesThatConsolidatedRewards.computeIfPresent(sponseeAddress, (k, v) -> v + count) == null) { + sponseesThatConsolidatedRewards.put(sponseeAddress, count); + } + } + + // Remove sponsees that have only sent a low number of payments to the filtered addresses + Map filteredSponseesThatConsolidatedRewards = sponseesThatConsolidatedRewards.entrySet().stream() + .filter(p -> p.getValue() != null && p.getValue() >= 2) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + this.consolidationCount = sponseesThatConsolidatedRewards.size(); + this.consolidatedAddresses = new LinkedHashSet<>(filteredSponseesThatConsolidatedRewards.keySet()); + this.suspiciousCount = this.consolidationCount + this.zeroTransactionAddreses.size(); + this.suspiciousPercent = (int)(this.suspiciousCount / (float) this.sponsees.size() * 100); + } + + private void findBulkIssuance() { + Long lastTimestamp = null; + for (RewardShareTransactionData rewardShareTransactionData : sponsorshipRewardShares) { + long timestamp = rewardShareTransactionData.getTimestamp(); + if (timestamp >= this.snapshotTimestamp) { + continue; + } + + if (lastTimestamp != null) { + if (timestamp - lastTimestamp < 3*60*1000L) { + this.bulkIssuanceCount++; + } + } + lastTimestamp = timestamp; + } + } + + private void findRegisteredNameCount() throws DataException { + int registeredNameCount = 0; + for (String sponseeAddress : sponsees) { + List names = repository.getNameRepository().getNamesByOwner(sponseeAddress); + for (NameData name : names) { + if (name.getRegistered() < this.snapshotTimestamp) { + registeredNameCount++; + break; + } + } + } + this.registeredNameCount = registeredNameCount; + } + + private void findRecentSponsorshipCount() { + final long referenceTimestamp = this.snapshotTimestamp - (365 * 24 * 60 * 60 * 1000L); + int recentSponsorshipCount = 0; + for (RewardShareTransactionData rewardShare : sponsorshipRewardShares) { + if (rewardShare.getTimestamp() >= referenceTimestamp) { + recentSponsorshipCount++; + } + } + this.recentSponsorshipCount = recentSponsorshipCount; + } + + private int calculateScore() { + final int suspiciousMultiplier = (this.suspiciousCount >= 100) ? this.suspiciousPercent : 1; + final int nameMultiplier = (this.sponsees.size() >= 50 && this.registeredNameCount == 0) ? 2 : 1; + final int consolidationMultiplier = Math.max(this.consolidationCount, 1); + final int bulkIssuanceMultiplier = Math.max(this.bulkIssuanceCount / 2, 1); + final int offset = 9; + return suspiciousMultiplier * nameMultiplier * consolidationMultiplier * bulkIssuanceMultiplier - offset; + } + + private void fetchSponsorshipRewardShares() throws DataException { + List sponsorshipRewardShares = new ArrayList<>(); + + // Define relevant transactions + List txTypes = List.of(TransactionType.REWARD_SHARE); + List transactionDataList = fetchTransactions(repository, txTypes, this.address, false); + + for (TransactionData transactionData : transactionDataList) { + if (transactionData.getType() != TransactionType.REWARD_SHARE) { + continue; + } + + RewardShareTransactionData rewardShareTransactionData = (RewardShareTransactionData) transactionData; + + // Skip removals + if (rewardShareTransactionData.getSharePercent() < 0) { + continue; + } + + // Skip if not sponsored by this account + if (!Arrays.equals(rewardShareTransactionData.getCreatorPublicKey(), accountData.getPublicKey())) { + continue; + } + + // Skip self shares + if (Objects.equals(rewardShareTransactionData.getRecipient(), this.address)) { + continue; + } + + boolean duplicateFound = false; + for (RewardShareTransactionData existingRewardShare : sponsorshipRewardShares) { + if (Objects.equals(existingRewardShare.getRecipient(), rewardShareTransactionData.getRecipient())) { + // Duplicate + duplicateFound = true; + break; + } + } + if (!duplicateFound) { + sponsorshipRewardShares.add(rewardShareTransactionData); + this.sponsees.add(rewardShareTransactionData.getRecipient()); + } + } + + this.sponsorshipRewardShares = sponsorshipRewardShares; + } + + private List fetchTransferPrivsForAddress(String address) throws DataException { + return fetchTransactions(repository, + List.of(TransactionType.TRANSFER_PRIVS), + address, true); + } + + private void fetchPaymentsForAddress(String address) throws DataException { + List payments = fetchTransactions(repository, + Arrays.asList(TransactionType.PAYMENT, TransactionType.TRANSFER_ASSET), + address, false); + this.paymentsByAddress.put(address, payments); + } + + private List fetchOutgoingPaymentRecipientsForAddress(String address) { + List outgoingPaymentRecipients = new ArrayList<>(); + + List transactionDataList = this.paymentsByAddress.get(address); + if (transactionDataList == null) transactionDataList = new ArrayList<>(); + transactionDataList.removeIf(t -> t.getTimestamp() >= this.snapshotTimestamp); + for (TransactionData transactionData : transactionDataList) { + switch (transactionData.getType()) { + + case PAYMENT: + PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData; + if (!Objects.equals(paymentTransactionData.getRecipient(), address)) { + // Outgoing payment from this account + outgoingPaymentRecipients.add(paymentTransactionData.getRecipient()); + } + break; + + case TRANSFER_ASSET: + TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) transactionData; + if (transferAssetTransactionData.getAssetId() == Asset.QORT) { + if (!Objects.equals(transferAssetTransactionData.getRecipient(), address)) { + // Outgoing payment from this account + outgoingPaymentRecipients.add(transferAssetTransactionData.getRecipient()); + } + } + break; + + default: + break; + } + } + + return outgoingPaymentRecipients; + } + + private boolean hasZeroTransactions(String address) { + List transactionDataList = this.paymentsByAddress.get(address); + if (transactionDataList == null) { + return true; + } + transactionDataList.removeIf(t -> t.getTimestamp() >= this.snapshotTimestamp); + return transactionDataList.size() == 0; + } + + private static List fetchTransactions(Repository repository, List txTypes, String address, boolean reverse) throws DataException { + // Fetch all relevant transactions for this account + List signatures = repository.getTransactionRepository() + .getSignaturesMatchingCriteria(null, null, null, txTypes, + null, null, address, TransactionsResource.ConfirmationStatus.CONFIRMED, + null, null, reverse); + + List transactionDataList = new ArrayList<>(); + + for (byte[] signature : signatures) { + // Fetch transaction data + TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); + if (transactionData == null) { + continue; + } + transactionDataList.add(transactionData); + } + + return transactionDataList; + } + +} diff --git a/src/main/java/org/qortal/api/model/AccountPenaltyStats.java b/src/main/java/org/qortal/api/model/AccountPenaltyStats.java new file mode 100644 index 00000000..aafe25fc --- /dev/null +++ b/src/main/java/org/qortal/api/model/AccountPenaltyStats.java @@ -0,0 +1,56 @@ +package org.qortal.api.model; + +import org.qortal.block.SelfSponsorshipAlgoV1Block; +import org.qortal.data.account.AccountData; +import org.qortal.data.naming.NameData; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import java.util.ArrayList; +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class AccountPenaltyStats { + + public Integer totalPenalties; + public Integer maxPenalty; + public Integer minPenalty; + public String penaltyHash; + + protected AccountPenaltyStats() { + } + + public AccountPenaltyStats(Integer totalPenalties, Integer maxPenalty, Integer minPenalty, String penaltyHash) { + this.totalPenalties = totalPenalties; + this.maxPenalty = maxPenalty; + this.minPenalty = minPenalty; + this.penaltyHash = penaltyHash; + } + + public static AccountPenaltyStats fromAccounts(List accounts) { + int totalPenalties = 0; + Integer maxPenalty = null; + Integer minPenalty = null; + + List addresses = new ArrayList<>(); + for (AccountData accountData : accounts) { + int penalty = accountData.getBlocksMintedPenalty(); + addresses.add(accountData.getAddress()); + totalPenalties++; + + // Penalties are expressed as a negative number, so the min and the max are reversed here + if (maxPenalty == null || penalty < maxPenalty) maxPenalty = penalty; + if (minPenalty == null || penalty > minPenalty) minPenalty = penalty; + } + + String penaltyHash = SelfSponsorshipAlgoV1Block.getHash(addresses); + return new AccountPenaltyStats(totalPenalties, maxPenalty, minPenalty, penaltyHash); + } + + + @Override + public String toString() { + return String.format("totalPenalties: %d, maxPenalty: %d, minPenalty: %d, penaltyHash: %s", totalPenalties, maxPenalty, minPenalty, penaltyHash == null ? "null" : penaltyHash); + } +} diff --git a/src/main/java/org/qortal/api/model/ConnectedPeer.java b/src/main/java/org/qortal/api/model/ConnectedPeer.java index 21bfc1f9..c4198654 100644 --- a/src/main/java/org/qortal/api/model/ConnectedPeer.java +++ b/src/main/java/org/qortal/api/model/ConnectedPeer.java @@ -1,7 +1,8 @@ package org.qortal.api.model; import io.swagger.v3.oas.annotations.media.Schema; -import org.qortal.data.network.PeerChainTipData; +import org.qortal.controller.Controller; +import org.qortal.data.block.BlockSummaryData; import org.qortal.data.network.PeerData; import org.qortal.network.Handshake; import org.qortal.network.Peer; @@ -36,6 +37,7 @@ public class ConnectedPeer { public Long lastBlockTimestamp; public UUID connectionId; public String age; + public Boolean isTooDivergent; protected ConnectedPeer() { } @@ -63,11 +65,16 @@ public class ConnectedPeer { this.age = "connecting..."; } - PeerChainTipData peerChainTipData = peer.getChainTipData(); + BlockSummaryData peerChainTipData = peer.getChainTipData(); if (peerChainTipData != null) { - this.lastHeight = peerChainTipData.getLastHeight(); - this.lastBlockSignature = peerChainTipData.getLastBlockSignature(); - this.lastBlockTimestamp = peerChainTipData.getLastBlockTimestamp(); + this.lastHeight = peerChainTipData.getHeight(); + this.lastBlockSignature = peerChainTipData.getSignature(); + this.lastBlockTimestamp = peerChainTipData.getTimestamp(); + } + + // Only include isTooDivergent decision if we've had the opportunity to request block summaries this peer + if (peer.getLastTooDivergentTime() != null) { + this.isTooDivergent = Controller.wasRecentlyTooDivergent.test(peer); } } diff --git a/src/main/java/org/qortal/api/model/NodeStatus.java b/src/main/java/org/qortal/api/model/NodeStatus.java index 9d08bb14..ab5e766e 100644 --- a/src/main/java/org/qortal/api/model/NodeStatus.java +++ b/src/main/java/org/qortal/api/model/NodeStatus.java @@ -4,6 +4,7 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import org.qortal.controller.Controller; +import org.qortal.controller.OnlineAccountsManager; import org.qortal.controller.Synchronizer; import org.qortal.network.Network; @@ -21,7 +22,7 @@ public class NodeStatus { public final int height; public NodeStatus() { - this.isMintingPossible = Controller.getInstance().isMintingPossible(); + this.isMintingPossible = OnlineAccountsManager.getInstance().hasActiveOnlineAccountSignatures(); this.syncPercent = Synchronizer.getInstance().getSyncPercent(); this.isSynchronizing = Synchronizer.getInstance().isSynchronizing(); diff --git a/src/main/java/org/qortal/api/model/crosschain/PirateChainSendRequest.java b/src/main/java/org/qortal/api/model/crosschain/PirateChainSendRequest.java new file mode 100644 index 00000000..e96bde01 --- /dev/null +++ b/src/main/java/org/qortal/api/model/crosschain/PirateChainSendRequest.java @@ -0,0 +1,32 @@ +package org.qortal.api.model.crosschain; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +@XmlAccessorType(XmlAccessType.FIELD) +public class PirateChainSendRequest { + + @Schema(description = "32 bytes of entropy, Base58 encoded", example = "5oSXF53qENtdUyKhqSxYzP57m6RhVFP9BJKRr9E5kRGV") + public String entropy58; + + @Schema(description = "Recipient's Pirate Chain address", example = "zc...") + public String receivingAddress; + + @Schema(description = "Amount of ARRR to send", type = "number") + @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) + public long arrrAmount; + + @Schema(description = "Transaction fee per byte (optional). Default is 0.00000100 ARRR (100 sats) per byte", example = "0.00000100", type = "number") + @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) + public Long feePerByte; + + @Schema(description = "Optional memo to include information for the recipient", example = "zc...") + public String memo; + + public PirateChainSendRequest() { + } + +} diff --git a/src/main/java/org/qortal/api/resource/AddressesResource.java b/src/main/java/org/qortal/api/resource/AddressesResource.java index 4de8d908..79cb6e05 100644 --- a/src/main/java/org/qortal/api/resource/AddressesResource.java +++ b/src/main/java/org/qortal/api/resource/AddressesResource.java @@ -14,6 +14,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; @@ -27,6 +28,7 @@ import org.qortal.api.ApiErrors; import org.qortal.api.ApiException; import org.qortal.api.ApiExceptionFactory; import org.qortal.api.Security; +import org.qortal.api.model.AccountPenaltyStats; import org.qortal.api.model.ApiOnlineAccount; import org.qortal.api.model.RewardShareKeyRequest; import org.qortal.asset.Asset; @@ -34,6 +36,7 @@ import org.qortal.controller.LiteNode; import org.qortal.controller.OnlineAccountsManager; import org.qortal.crypto.Crypto; import org.qortal.data.account.AccountData; +import org.qortal.data.account.AccountPenaltyData; import org.qortal.data.account.RewardShareData; import org.qortal.data.network.OnlineAccountData; import org.qortal.data.network.OnlineAccountLevel; @@ -205,6 +208,10 @@ public class AddressesResource { try (final Repository repository = RepositoryManager.getRepository()) { List onlineAccountLevels = new ArrayList<>(); + // Prepopulate all levels + for (int i=0; i<=10; i++) + onlineAccountLevels.add(new OnlineAccountLevel(i, 0)); + for (OnlineAccountData onlineAccountData : onlineAccounts) { try { final int minterLevel = Account.getRewardShareEffectiveMintingLevelIncludingLevelZero(repository, onlineAccountData.getPublicKey()); @@ -467,6 +474,54 @@ public class AddressesResource { } } + @GET + @Path("/penalties") + @Operation( + summary = "Get addresses with penalties", + description = "Returns a list of accounts with a blocksMintedPenalty", + responses = { + @ApiResponse( + description = "accounts with penalties", + content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = AccountPenaltyData.class))) + ) + } + ) + @ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) + public List getAccountsWithPenalties() { + try (final Repository repository = RepositoryManager.getRepository()) { + + List accounts = repository.getAccountRepository().getPenaltyAccounts(); + List penalties = accounts.stream().map(a -> new AccountPenaltyData(a.getAddress(), a.getBlocksMintedPenalty())).collect(Collectors.toList()); + + return penalties; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @GET + @Path("/penalties/stats") + @Operation( + summary = "Get stats about current penalties", + responses = { + @ApiResponse( + description = "aggregated stats about accounts with penalties", + content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = AccountPenaltyStats.class))) + ) + } + ) + @ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) + public AccountPenaltyStats getPenaltyStats() { + try (final Repository repository = RepositoryManager.getRepository()) { + + List accounts = repository.getAccountRepository().getPenaltyAccounts(); + return AccountPenaltyStats.fromAccounts(accounts); + + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + @POST @Path("/publicize") @Operation( diff --git a/src/main/java/org/qortal/api/resource/AdminResource.java b/src/main/java/org/qortal/api/resource/AdminResource.java index bf7294ab..9cff1bbb 100644 --- a/src/main/java/org/qortal/api/resource/AdminResource.java +++ b/src/main/java/org/qortal/api/resource/AdminResource.java @@ -125,12 +125,12 @@ public class AdminResource { } private String getNodeType() { - if (Settings.getInstance().isTopOnly()) { - return "topOnly"; - } - else if (Settings.getInstance().isLite()) { + if (Settings.getInstance().isLite()) { return "lite"; } + else if (Settings.getInstance().isTopOnly()) { + return "topOnly"; + } else { return "full"; } @@ -728,6 +728,49 @@ public class AdminResource { } } + @POST + @Path("/repository/importarchivedtrades") + @Operation( + summary = "Imports archived trades from TradeBotStatesArchive.json", + description = "This can be used to recover trades that exist in the archive only, which may be needed if a
" + + "problem occurred during the proof-of-work computation stage of a buy request.", + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean")) + ) + } + ) + @ApiErrors({ApiError.REPOSITORY_ISSUE}) + @SecurityRequirement(name = "apiKey") + public boolean importArchivedTrades(@HeaderParam(Security.API_KEY_HEADER) String apiKey) { + Security.checkApiCallAllowed(request); + + try (final Repository repository = RepositoryManager.getRepository()) { + ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock(); + + blockchainLock.lockInterruptibly(); + + try { + repository.importDataFromFile("qortal-backup/TradeBotStatesArchive.json"); + repository.saveChanges(); + + return true; + + } catch (IOException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA, e); + + } finally { + blockchainLock.unlock(); + } + } catch (InterruptedException e) { + // We couldn't lock blockchain to perform import + return false; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @POST @Path("/apikey/generate") diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 451d9b8a..0df81d9b 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -12,10 +12,10 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import java.io.*; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.servlet.ServletContext; @@ -45,6 +45,7 @@ import org.qortal.data.arbitrary.*; import org.qortal.data.naming.NameData; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.TransactionData; +import org.qortal.list.ResourceListManager; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; @@ -56,6 +57,7 @@ import org.qortal.transaction.Transaction.ValidationResult; import org.qortal.transform.TransformationException; import org.qortal.transform.transaction.ArbitraryTransactionTransformer; import org.qortal.transform.transaction.TransactionTransformer; +import org.qortal.utils.ArbitraryTransactionUtils; import org.qortal.utils.Base58; import org.qortal.utils.NTP; import org.qortal.utils.ZipUtils; @@ -91,6 +93,7 @@ public class ArbitraryResource { @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse, + @Parameter(description = "Filter names by list") @QueryParam("namefilter") String nameFilter, @Parameter(description = "Include status") @QueryParam("includestatus") Boolean includeStatus, @Parameter(description = "Include metadata") @QueryParam("includemetadata") Boolean includeMetadata) { @@ -107,8 +110,18 @@ public class ArbitraryResource { throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "identifier cannot be specified when requesting a default resource"); } + // Load filter from list if needed + List names = null; + if (nameFilter != null) { + names = ResourceListManager.getInstance().getStringsInList(nameFilter); + if (names.isEmpty()) { + // List doesn't exist or is empty - so there will be no matches + return new ArrayList<>(); + } + } + List resources = repository.getArbitraryRepository() - .getArbitraryResources(service, identifier, null, defaultRes, limit, offset, reverse); + .getArbitraryResources(service, identifier, names, defaultRes, limit, offset, reverse); if (resources == null) { return new ArrayList<>(); @@ -216,7 +229,7 @@ public class ArbitraryResource { String name = creatorName.name; if (name != null) { List resources = repository.getArbitraryRepository() - .getArbitraryResources(service, identifier, name, defaultRes, null, null, reverse); + .getArbitraryResources(service, identifier, Arrays.asList(name), defaultRes, null, null, reverse); if (includeStatus != null && includeStatus) { resources = this.addStatusToResources(resources); @@ -254,7 +267,7 @@ public class ArbitraryResource { @QueryParam("build") Boolean build) { Security.requirePriorAuthorizationOrApiKey(request, name, service, null); - return this.getStatus(service, name, null, build); + return ArbitraryTransactionUtils.getStatus(service, name, null, build); } @GET @@ -276,7 +289,7 @@ public class ArbitraryResource { @QueryParam("build") Boolean build) { Security.requirePriorAuthorizationOrApiKey(request, name, service, identifier); - return this.getStatus(service, name, identifier, build); + return ArbitraryTransactionUtils.getStatus(service, name, identifier, build); } @@ -706,7 +719,7 @@ public class ArbitraryResource { try { ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, false); if (transactionMetadata != null) { - ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata); + ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata, true); if (resourceMetadata != null) { return resourceMetadata; } @@ -1115,7 +1128,7 @@ public class ArbitraryResource { if (path == null) { // See if we have a string instead if (string != null) { - File tempFile = File.createTempFile("qortal-", ".tmp"); + File tempFile = File.createTempFile("qortal-", ""); tempFile.deleteOnExit(); BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile.toPath().toString())); writer.write(string); @@ -1125,7 +1138,7 @@ public class ArbitraryResource { } // ... or base64 encoded raw data else if (base64 != null) { - File tempFile = File.createTempFile("qortal-", ".tmp"); + File tempFile = File.createTempFile("qortal-", ""); tempFile.deleteOnExit(); Files.write(tempFile.toPath(), Base64.decode(base64)); path = tempFile.toPath().toString(); @@ -1247,24 +1260,6 @@ public class ArbitraryResource { } - private ArbitraryResourceStatus getStatus(Service service, String name, String identifier, Boolean build) { - - // If "build=true" has been specified in the query string, build the resource before returning its status - if (build != null && build == true) { - ArbitraryDataReader reader = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, null); - try { - if (!reader.isBuilding()) { - reader.loadSynchronously(false); - } - } catch (Exception e) { - // No need to handle exception, as it will be reflected in the status - } - } - - ArbitraryDataResource resource = new ArbitraryDataResource(name, ResourceIdType.NAME, service, identifier); - return resource.getStatus(false); - } - private List addStatusToResources(List resources) { // Determine and add the status of each resource List updatedResources = new ArrayList<>(); @@ -1293,7 +1288,7 @@ public class ArbitraryResource { ArbitraryDataResource resource = new ArbitraryDataResource(resourceInfo.name, ResourceIdType.NAME, resourceInfo.service, resourceInfo.identifier); ArbitraryDataTransactionMetadata transactionMetadata = resource.getLatestTransactionMetadata(); - ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata); + ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata, false); if (resourceMetadata != null) { resourceInfo.metadata = resourceMetadata; } diff --git a/src/main/java/org/qortal/api/resource/BlocksResource.java b/src/main/java/org/qortal/api/resource/BlocksResource.java index 28d54a44..15541802 100644 --- a/src/main/java/org/qortal/api/resource/BlocksResource.java +++ b/src/main/java/org/qortal/api/resource/BlocksResource.java @@ -114,7 +114,7 @@ public class BlocksResource { @Path("/signature/{signature}/data") @Operation( summary = "Fetch serialized, base58 encoded block data using base58 signature", - description = "Returns serialized data for the block that matches the given signature", + description = "Returns serialized data for the block that matches the given signature, and an optional block serialization version", responses = { @ApiResponse( description = "the block data", @@ -125,7 +125,7 @@ public class BlocksResource { @ApiErrors({ ApiError.INVALID_SIGNATURE, ApiError.BLOCK_UNKNOWN, ApiError.INVALID_DATA, ApiError.REPOSITORY_ISSUE }) - public String getSerializedBlockData(@PathParam("signature") String signature58) { + public String getSerializedBlockData(@PathParam("signature") String signature58, @QueryParam("version") Integer version) { // Decode signature byte[] signature; try { @@ -136,20 +136,41 @@ public class BlocksResource { try (final Repository repository = RepositoryManager.getRepository()) { + // Default to version 1 + if (version == null) { + version = 1; + } + // Check the database first BlockData blockData = repository.getBlockRepository().fromSignature(signature); if (blockData != null) { Block block = new Block(repository, blockData); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); bytes.write(Ints.toByteArray(block.getBlockData().getHeight())); - bytes.write(BlockTransformer.toBytes(block)); + + switch (version) { + case 1: + bytes.write(BlockTransformer.toBytes(block)); + break; + + case 2: + bytes.write(BlockTransformer.toBytesV2(block)); + break; + + default: + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + } + return Base58.encode(bytes.toByteArray()); } // Not found, so try the block archive byte[] bytes = BlockArchiveReader.getInstance().fetchSerializedBlockBytesForSignature(signature, false, repository); if (bytes != null) { - return Base58.encode(bytes); + if (version != 1) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Archived blocks require version 1"); + } + return Base58.encode(bytes); } throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN); @@ -613,13 +634,16 @@ public class BlocksResource { @ApiErrors({ ApiError.REPOSITORY_ISSUE }) - public List getBlockRange(@PathParam("height") int height, @Parameter( - ref = "count" - ) @QueryParam("count") int count) { + public List getBlockRange(@PathParam("height") int height, + @Parameter(ref = "count") @QueryParam("count") int count, + @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse, + @QueryParam("includeOnlineSignatures") Boolean includeOnlineSignatures) { try (final Repository repository = RepositoryManager.getRepository()) { List blocks = new ArrayList<>(); + boolean shouldReverse = (reverse != null && reverse == true); - for (/* count already set */; count > 0; --count, ++height) { + int i = 0; + while (i < count) { BlockData blockData = repository.getBlockRepository().fromHeight(height); if (blockData == null) { // Not found - try the archive @@ -629,8 +653,14 @@ public class BlocksResource { break; } } + if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + blockData.setOnlineAccountsSignatures(null); + } blocks.add(blockData); + + height = shouldReverse ? height - 1 : height + 1; + i++; } return blocks; diff --git a/src/main/java/org/qortal/api/resource/ChatResource.java b/src/main/java/org/qortal/api/resource/ChatResource.java index be8bd7d7..2601e938 100644 --- a/src/main/java/org/qortal/api/resource/ChatResource.java +++ b/src/main/java/org/qortal/api/resource/ChatResource.java @@ -69,6 +69,9 @@ public class ChatResource { public List searchChat(@QueryParam("before") Long before, @QueryParam("after") Long after, @QueryParam("txGroupId") Integer txGroupId, @QueryParam("involving") List involvingAddresses, + @QueryParam("reference") String reference, + @QueryParam("chatreference") String chatReference, + @QueryParam("haschatreference") Boolean hasChatReference, @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) { @@ -87,11 +90,22 @@ public class ChatResource { if (after != null && after < 1500000000000L) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + byte[] referenceBytes = null; + if (reference != null) + referenceBytes = Base58.decode(reference); + + byte[] chatReferenceBytes = null; + if (chatReference != null) + chatReferenceBytes = Base58.decode(chatReference); + try (final Repository repository = RepositoryManager.getRepository()) { return repository.getChatRepository().getMessagesMatchingCriteria( before, after, txGroupId, + referenceBytes, + chatReferenceBytes, + hasChatReference, involvingAddresses, limit, offset, reverse); } catch (DataException e) { @@ -99,6 +113,38 @@ public class ChatResource { } } + @GET + @Path("/message/{signature}") + @Operation( + summary = "Find chat message by signature", + responses = { + @ApiResponse( + description = "CHAT message", + content = @Content( + schema = @Schema( + implementation = ChatMessage.class + ) + ) + ) + } + ) + @ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) + public ChatMessage getMessageBySignature(@PathParam("signature") String signature58) { + byte[] signature = Base58.decode(signature58); + + try (final Repository repository = RepositoryManager.getRepository()) { + + ChatTransactionData chatTransactionData = (ChatTransactionData) repository.getTransactionRepository().fromSignature(signature); + if (chatTransactionData == null) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Message not found"); + } + + return repository.getChatRepository().toChatMessage(chatTransactionData); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + @GET @Path("/active/{address}") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java index 80d19804..dd967451 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java @@ -68,7 +68,7 @@ public class CrossChainBitcoinResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); try { - Long balance = bitcoin.getWalletBalanceFromTransactions(key58); + Long balance = bitcoin.getWalletBalance(key58); if (balance == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); diff --git a/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java b/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java index 57049639..31d51c73 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java @@ -68,7 +68,7 @@ public class CrossChainDigibyteResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); try { - Long balance = digibyte.getWalletBalanceFromTransactions(key58); + Long balance = digibyte.getWalletBalance(key58); if (balance == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); diff --git a/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java index 189a53d3..28bebfb8 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java @@ -66,7 +66,7 @@ public class CrossChainDogecoinResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); try { - Long balance = dogecoin.getWalletBalanceFromTransactions(key58); + Long balance = dogecoin.getWalletBalance(key58); if (balance == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); diff --git a/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java b/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java index fbcde1a6..45b92c7c 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java @@ -1,5 +1,6 @@ package org.qortal.api.resource; +import com.google.common.hash.HashCode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -9,6 +10,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.math.BigDecimal; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; @@ -284,6 +287,12 @@ public class CrossChainHtlcResource { continue; } + Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain(); + if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) { + LOGGER.info("Skipping AT {} because ARRR is currently unsupported", atAddress); + continue; + } + CrossChainTradeData crossChainTradeData = acct.populateTradeData(repository, atData); if (crossChainTradeData == null) { LOGGER.info("Couldn't find crosschain trade data for AT {}", atAddress); @@ -363,10 +372,6 @@ public class CrossChainHtlcResource { // Use secret-A to redeem P2SH-A Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain(); - if (bitcoiny.getClass() == Bitcoin.class) { - LOGGER.info("Redeeming a Bitcoin HTLC is not yet supported"); - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); - } int lockTime = crossChainTradeData.lockTimeA; byte[] redeemScriptA = BitcoinyHTLC.buildScript(crossChainTradeData.partnerForeignPKH, lockTime, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretA); @@ -574,70 +579,108 @@ public class CrossChainHtlcResource { // If the AT is "finished" then it will have a zero balance // In these cases we should avoid HTLC refunds if tbe QORT haven't been returned to the seller if (atData.getIsFinished() && crossChainTradeData.mode != AcctMode.REFUNDED && crossChainTradeData.mode != AcctMode.CANCELLED) { - LOGGER.info(String.format("Skipping AT %s because the QORT has already been redemed", atAddress)); + LOGGER.info(String.format("Skipping AT %s because the QORT has already been redeemed by the buyer", atAddress)); throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); } List allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData(); - TradeBotData tradeBotData = allTradeBotData.stream().filter(tradeBotDataItem -> tradeBotDataItem.getAtAddress().equals(atAddress)).findFirst().orElse(null); - if (tradeBotData == null) + List tradeBotDataList = allTradeBotData.stream().filter(tradeBotDataItem -> tradeBotDataItem.getAtAddress().equals(atAddress)).collect(Collectors.toList()); + if (tradeBotDataList == null || tradeBotDataList.isEmpty()) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); - Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain(); - if (bitcoiny.getClass() == Bitcoin.class) { - LOGGER.info("Refunding a Bitcoin HTLC is not yet supported"); - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); - } + // Loop through all matching entries for this AT address, as there might be more than one + for (TradeBotData tradeBotData : tradeBotDataList) { - int lockTime = tradeBotData.getLockTimeA(); + if (tradeBotData == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); - // We can't refund P2SH-A until lockTime-A has passed - if (NTP.getTime() <= lockTime * 1000L) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON); + Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain(); + int lockTime = tradeBotData.getLockTimeA(); - // We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113) - int medianBlockTime = bitcoiny.getMedianBlockTime(); - if (medianBlockTime <= lockTime) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON); + // We can't refund P2SH-A until lockTime-A has passed + if (NTP.getTime() <= lockTime * 1000L) + continue; - byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); - String p2shAddressA = bitcoiny.deriveP2shAddress(redeemScriptA); - LOGGER.info(String.format("Refunding P2SH address: %s", p2shAddressA)); + // We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113) + int medianBlockTime = bitcoiny.getMedianBlockTime(); + if (medianBlockTime <= lockTime) + continue; - // Fee for redeem/refund is subtracted from P2SH-A balance. - long feeTimestamp = calcFeeTimestamp(lockTime, crossChainTradeData.tradeTimeout); - long p2shFee = bitcoiny.getP2shFee(feeTimestamp); - long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee; - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA); + // Fee for redeem/refund is subtracted from P2SH-A balance. + long feeTimestamp = calcFeeTimestamp(lockTime, crossChainTradeData.tradeTimeout); + long p2shFee = bitcoiny.getP2shFee(feeTimestamp); + long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee; - switch (htlcStatusA) { - case UNFUNDED: - case FUNDING_IN_PROGRESS: - // Still waiting for P2SH-A to be funded... - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON); + // Create redeem script based on destination chain + byte[] redeemScriptA; + String p2shAddressA; + BitcoinyHTLC.Status htlcStatusA; + if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) { + redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); + p2shAddressA = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptA); + htlcStatusA = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA); + } else { + redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); + p2shAddressA = bitcoiny.deriveP2shAddress(redeemScriptA); + htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA); + } + LOGGER.info(String.format("Refunding P2SH address: %s", p2shAddressA)); - case REDEEM_IN_PROGRESS: - case REDEEMED: - case REFUND_IN_PROGRESS: - case REFUNDED: - // Too late! - return false; + switch (htlcStatusA) { + case UNFUNDED: + case FUNDING_IN_PROGRESS: + // Still waiting for P2SH-A to be funded... + continue; - case FUNDED:{ - Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount); - ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); - List fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddressA); + case REDEEM_IN_PROGRESS: + case REDEEMED: + case REFUND_IN_PROGRESS: + case REFUNDED: + // Too late! + continue; - // Validate the destination foreign blockchain address - Address receiving = Address.fromString(bitcoiny.getNetworkParameters(), receiveAddress); - if (receiving.getOutputScriptType() != Script.ScriptType.P2PKH) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + case FUNDED: { + Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount); - Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey, - fundingOutputs, redeemScriptA, lockTime, receiving.getHash()); + if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) { + // Pirate Chain custom integration - bitcoiny.broadcastTransaction(p2shRefundTransaction); - return true; + PirateChain pirateChain = PirateChain.getInstance(); + String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA); + + // Get funding txid + String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA); + if (fundingTxidHex == null) { + throw new ForeignBlockchainException("Missing funding txid when refunding P2SH"); + } + String fundingTxid58 = Base58.encode(HashCode.fromString(fundingTxidHex).asBytes()); + + byte[] privateKey = tradeBotData.getTradePrivateKey(); + String privateKey58 = Base58.encode(privateKey); + String redeemScript58 = Base58.encode(redeemScriptA); + + String txid = PirateChain.getInstance().refundP2sh(p2shAddressT3, + receiveAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTime, privateKey58); + LOGGER.info("Refund txid: {}", txid); + } else { + // ElectrumX coins + + ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); + List fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddressA); + + // Validate the destination foreign blockchain address + Address receiving = Address.fromString(bitcoiny.getNetworkParameters(), receiveAddress); + if (receiving.getOutputScriptType() != Script.ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey, + fundingOutputs, redeemScriptA, lockTime, receiving.getHash()); + + bitcoiny.broadcastTransaction(p2shRefundTransaction); + } + + return true; + } } } diff --git a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java index 8ac0f9a0..d12dd94c 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java @@ -68,7 +68,7 @@ public class CrossChainLitecoinResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); try { - Long balance = litecoin.getWalletBalanceFromTransactions(key58); + Long balance = litecoin.getWalletBalance(key58); if (balance == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); diff --git a/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java new file mode 100644 index 00000000..bd7bf57d --- /dev/null +++ b/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java @@ -0,0 +1,229 @@ +package org.qortal.api.resource; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.qortal.api.ApiError; +import org.qortal.api.ApiErrors; +import org.qortal.api.ApiExceptionFactory; +import org.qortal.api.Security; +import org.qortal.api.model.crosschain.PirateChainSendRequest; +import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.PirateChain; +import org.qortal.crosschain.SimpleTransaction; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import java.util.List; + +@Path("/crosschain/arrr") +@Tag(name = "Cross-Chain (Pirate Chain)") +public class CrossChainPirateChainResource { + + @Context + HttpServletRequest request; + + @POST + @Path("/walletbalance") + @Operation( + summary = "Returns ARRR balance", + description = "Supply 32 bytes of entropy, Base58 encoded", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "32 bytes of entropy, Base58 encoded", + example = "5oSXF53qENtdUyKhqSxYzP57m6RhVFP9BJKRr9E5kRGV" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", description = "balance (satoshis)")) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String getPirateChainWalletBalance(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String entropy58) { + Security.checkApiCallAllowed(request); + + PirateChain pirateChain = PirateChain.getInstance(); + + try { + Long balance = pirateChain.getWalletBalance(entropy58); + if (balance == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + + return balance.toString(); + + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE, e.getMessage()); + } + } + + @POST + @Path("/wallettransactions") + @Operation( + summary = "Returns transactions", + description = "Supply 32 bytes of entropy, Base58 encoded", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "32 bytes of entropy, Base58 encoded", + example = "5oSXF53qENtdUyKhqSxYzP57m6RhVFP9BJKRr9E5kRGV" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public List getPirateChainWalletTransactions(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String entropy58) { + Security.checkApiCallAllowed(request); + + PirateChain pirateChain = PirateChain.getInstance(); + + try { + return pirateChain.getWalletTransactions(entropy58); + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE, e.getMessage()); + } + } + + @POST + @Path("/send") + @Operation( + summary = "Sends ARRR from wallet", + description = "Currently supports 'legacy' P2PKH PirateChain addresses and Native SegWit (P2WPKH) addresses. Supply BIP32 'm' private key in base58, starting with 'xprv' for mainnet, 'tprv' for testnet", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "32 bytes of entropy, Base58 encoded", + example = "5oSXF53qENtdUyKhqSxYzP57m6RhVFP9BJKRr9E5kRGV" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", description = "transaction hash")) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.FOREIGN_BLOCKCHAIN_BALANCE_ISSUE, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String sendBitcoin(@HeaderParam(Security.API_KEY_HEADER) String apiKey, PirateChainSendRequest pirateChainSendRequest) { + Security.checkApiCallAllowed(request); + + if (pirateChainSendRequest.arrrAmount <= 0) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + if (pirateChainSendRequest.feePerByte != null && pirateChainSendRequest.feePerByte <= 0) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + PirateChain pirateChain = PirateChain.getInstance(); + + try { + return pirateChain.sendCoins(pirateChainSendRequest); + + } catch (ForeignBlockchainException e) { + // TODO + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE, e.getMessage()); + } + } + + + @POST + @Path("/walletaddress") + @Operation( + summary = "Returns main wallet address", + description = "Supply 32 bytes of entropy, Base58 encoded", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "32 bytes of entropy, Base58 encoded", + example = "5oSXF53qENtdUyKhqSxYzP57m6RhVFP9BJKRr9E5kRGV" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String getPirateChainWalletAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String entropy58) { + Security.checkApiCallAllowed(request); + + PirateChain pirateChain = PirateChain.getInstance(); + + try { + return pirateChain.getWalletAddress(entropy58); + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE, e.getMessage()); + } + } + + + @POST + @Path("/syncstatus") + @Operation( + summary = "Returns synchronization status", + description = "Supply 32 bytes of entropy, Base58 encoded", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "32 bytes of entropy, Base58 encoded", + example = "5oSXF53qENtdUyKhqSxYzP57m6RhVFP9BJKRr9E5kRGV" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String getPirateChainSyncStatus(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String entropy58) { + Security.checkApiCallAllowed(request); + + PirateChain pirateChain = PirateChain.getInstance(); + + try { + return pirateChain.getSyncStatus(entropy58); + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE, e.getMessage()); + } + } +} diff --git a/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java index 756b0bb5..97550392 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java @@ -68,7 +68,7 @@ public class CrossChainRavencoinResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); try { - Long balance = ravencoin.getWalletBalanceFromTransactions(key58); + Long balance = ravencoin.getWalletBalance(key58); if (balance == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); diff --git a/src/main/java/org/qortal/api/resource/CrossChainTradeBotResource.java b/src/main/java/org/qortal/api/resource/CrossChainTradeBotResource.java index 66800eb7..aefca016 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainTradeBotResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainTradeBotResource.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; @@ -38,9 +39,12 @@ import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; import org.qortal.data.crosschain.CrossChainTradeData; import org.qortal.data.crosschain.TradeBotData; +import org.qortal.data.transaction.MessageTransactionData; +import org.qortal.data.transaction.TransactionData; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; +import org.qortal.transaction.Transaction; import org.qortal.utils.Base58; import org.qortal.utils.NTP; @@ -155,7 +159,7 @@ public class CrossChainTradeBotResource { return Base58.encode(unsignedBytes); } catch (DataException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage()); } } @@ -223,6 +227,17 @@ public class CrossChainTradeBotResource { if (crossChainTradeData.mode != AcctMode.OFFERING) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + // Check if there is a buy or a cancel request in progress for this trade + List txTypes = List.of(Transaction.TransactionType.MESSAGE); + List unconfirmed = repository.getTransactionRepository().getUnconfirmedTransactions(txTypes, null, 0, 0, false); + for (TransactionData transactionData : unconfirmed) { + MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData; + if (Objects.equals(messageTransactionData.getRecipient(), atAddress)) { + // There is a pending request for this trade, so block this buy attempt to reduce the risk of refunds + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Trade has an existing buy request or is pending cancellation."); + } + } + AcctTradeBot.ResponseResult result = TradeBot.getInstance().startResponse(repository, atData, acct, crossChainTradeData, tradeBotRespondRequest.foreignKey, tradeBotRespondRequest.receivingAddress); @@ -240,7 +255,7 @@ public class CrossChainTradeBotResource { return "false"; } } catch (DataException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage()); } } diff --git a/src/main/java/org/qortal/api/resource/TransactionsResource.java b/src/main/java/org/qortal/api/resource/TransactionsResource.java index 75724310..2b9b28a1 100644 --- a/src/main/java/org/qortal/api/resource/TransactionsResource.java +++ b/src/main/java/org/qortal/api/resource/TransactionsResource.java @@ -748,7 +748,7 @@ public class TransactionsResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE); ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock(); - if (!blockchainLock.tryLock(30, TimeUnit.SECONDS)) + if (!blockchainLock.tryLock(60, TimeUnit.SECONDS)) throw createTransactionInvalidException(request, ValidationResult.NO_BLOCKCHAIN_LOCK); try { diff --git a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java index 3dc2d494..76ed936c 100644 --- a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java @@ -46,6 +46,9 @@ public class ChatMessagesWebSocket extends ApiWebSocket { null, txGroupId, null, + null, + null, + null, null, null, null); sendMessages(session, chatMessages); @@ -69,6 +72,9 @@ public class ChatMessagesWebSocket extends ApiWebSocket { try (final Repository repository = RepositoryManager.getRepository()) { List chatMessages = repository.getChatRepository().getMessagesMatchingCriteria( + null, + null, + null, null, null, null, diff --git a/src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java b/src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java index 55969c6b..8d7a13cd 100644 --- a/src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java @@ -2,10 +2,7 @@ package org.qortal.api.websocket; import java.io.IOException; import java.io.StringWriter; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import org.eclipse.jetty.websocket.api.Session; @@ -85,6 +82,7 @@ public class TradeBotWebSocket extends ApiWebSocket implements Listener { @Override public void onWebSocketConnect(Session session) { Map> queryParams = session.getUpgradeRequest().getParameterMap(); + final boolean excludeInitialData = queryParams.get("excludeInitialData") != null; List foreignBlockchains = queryParams.get("foreignBlockchain"); final String foreignBlockchain = foreignBlockchains == null ? null : foreignBlockchains.get(0); @@ -98,15 +96,22 @@ public class TradeBotWebSocket extends ApiWebSocket implements Listener { // save session's preferred blockchain (if any) sessionBlockchain.put(session, foreignBlockchain); - // Send all known trade-bot entries - try (final Repository repository = RepositoryManager.getRepository()) { - List tradeBotEntries = repository.getCrossChainRepository().getAllTradeBotData(); - // Optional filtering - if (foreignBlockchain != null) - tradeBotEntries = tradeBotEntries.stream() - .filter(tradeBotData -> tradeBotData.getForeignBlockchain().equals(foreignBlockchain)) - .collect(Collectors.toList()); + + // Maybe send all known trade-bot entries + try (final Repository repository = RepositoryManager.getRepository()) { + List tradeBotEntries = new ArrayList<>(); + + // We might need to exclude the initial data from the response + if (!excludeInitialData) { + tradeBotEntries = repository.getCrossChainRepository().getAllTradeBotData(); + + // Optional filtering + if (foreignBlockchain != null) + tradeBotEntries = tradeBotEntries.stream() + .filter(tradeBotData -> tradeBotData.getForeignBlockchain().equals(foreignBlockchain)) + .collect(Collectors.toList()); + } if (!sendEntries(session, tradeBotEntries)) { session.close(4002, "websocket issue"); diff --git a/src/main/java/org/qortal/api/websocket/TradeOffersWebSocket.java b/src/main/java/org/qortal/api/websocket/TradeOffersWebSocket.java index 35fc4691..78c53dc3 100644 --- a/src/main/java/org/qortal/api/websocket/TradeOffersWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/TradeOffersWebSocket.java @@ -173,6 +173,7 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener { public void onWebSocketConnect(Session session) { Map> queryParams = session.getUpgradeRequest().getParameterMap(); final boolean includeHistoric = queryParams.get("includeHistoric") != null; + final boolean excludeInitialData = queryParams.get("excludeInitialData") != null; List foreignBlockchains = queryParams.get("foreignBlockchain"); final String foreignBlockchain = foreignBlockchains == null ? null : foreignBlockchains.get(0); @@ -189,20 +190,23 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener { List crossChainOfferSummaries = new ArrayList<>(); - synchronized (cachedInfoByBlockchain) { - Collection cachedInfos; + // We might need to exclude the initial data from the response + if (!excludeInitialData) { + synchronized (cachedInfoByBlockchain) { + Collection cachedInfos; - if (foreignBlockchain == null) - // No preferred blockchain, so iterate through all of them - cachedInfos = cachedInfoByBlockchain.values(); - else - cachedInfos = Collections.singleton(cachedInfoByBlockchain.computeIfAbsent(foreignBlockchain, k -> new CachedOfferInfo())); + if (foreignBlockchain == null) + // No preferred blockchain, so iterate through all of them + cachedInfos = cachedInfoByBlockchain.values(); + else + cachedInfos = Collections.singleton(cachedInfoByBlockchain.computeIfAbsent(foreignBlockchain, k -> new CachedOfferInfo())); - for (CachedOfferInfo cachedInfo : cachedInfos) { - crossChainOfferSummaries.addAll(cachedInfo.currentSummaries.values()); + for (CachedOfferInfo cachedInfo : cachedInfos) { + crossChainOfferSummaries.addAll(cachedInfo.currentSummaries.values()); - if (includeHistoric) - crossChainOfferSummaries.addAll(cachedInfo.historicSummaries.values()); + if (includeHistoric) + crossChainOfferSummaries.addAll(cachedInfo.historicSummaries.values()); + } } } diff --git a/src/main/java/org/qortal/api/websocket/TradePresenceWebSocket.java b/src/main/java/org/qortal/api/websocket/TradePresenceWebSocket.java index e9558599..ba9a8085 100644 --- a/src/main/java/org/qortal/api/websocket/TradePresenceWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/TradePresenceWebSocket.java @@ -65,11 +65,15 @@ public class TradePresenceWebSocket extends ApiWebSocket implements Listener { @Override public void onWebSocketConnect(Session session) { Map> queryParams = session.getUpgradeRequest().getParameterMap(); + final boolean excludeInitialData = queryParams.get("excludeInitialData") != null; - List tradePresences; + List tradePresences = new ArrayList<>(); - synchronized (currentEntries) { - tradePresences = List.copyOf(currentEntries.values()); + // We might need to exclude the initial data from the response + if (!excludeInitialData) { + synchronized (currentEntries) { + tradePresences = List.copyOf(currentEntries.values()); + } } if (!sendTradePresences(session, tradePresences)) { diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java index 4f0e3835..b6b17ea5 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java @@ -2,6 +2,7 @@ package org.qortal.arbitrary; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.arbitrary.exception.DataNotPublishedException; import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.metadata.ArbitraryDataMetadataCache; import org.qortal.arbitrary.misc.Service; @@ -88,7 +89,7 @@ public class ArbitraryDataBuilder { if (latestPut == null) { String message = String.format("Couldn't find PUT transaction for name %s, service %s and identifier %s", this.name, this.service, this.identifierString()); - throw new DataException(message); + throw new DataNotPublishedException(message); } this.latestPutTransaction = latestPut; diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java index 568549d8..78723958 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java @@ -4,6 +4,7 @@ import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.arbitrary.exception.DataNotPublishedException; import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.misc.Service; import org.qortal.controller.arbitrary.ArbitraryDataBuildManager; @@ -59,6 +60,9 @@ public class ArbitraryDataReader { private int layerCount; private byte[] latestSignature; + // The resource being read + ArbitraryDataResource arbitraryDataResource = null; + public ArbitraryDataReader(String resourceId, ResourceIdType resourceIdType, Service service, String identifier) { // Ensure names are always lowercase if (resourceIdType == ResourceIdType.NAME) { @@ -115,6 +119,11 @@ public class ArbitraryDataReader { return new ArbitraryDataBuildQueueItem(this.resourceId, this.resourceIdType, this.service, this.identifier); } + private ArbitraryDataResource createArbitraryDataResource() { + return new ArbitraryDataResource(this.resourceId, this.resourceIdType, this.service, this.identifier); + } + + /** * loadAsynchronously * @@ -162,6 +171,8 @@ public class ArbitraryDataReader { return; } + this.arbitraryDataResource = this.createArbitraryDataResource(); + this.preExecute(); this.deleteExistingFiles(); this.fetch(); @@ -169,9 +180,18 @@ public class ArbitraryDataReader { this.uncompress(); this.validate(); - } catch (DataException e) { + } catch (DataNotPublishedException e) { + if (e.getMessage() != null) { + // Log the message only, to avoid spamming the logs with a full stack trace + LOGGER.debug("DataNotPublishedException when trying to load QDN resource: {}", e.getMessage()); + } this.deleteWorkingDirectory(); - throw new DataException(e.getMessage()); + throw e; + + } catch (DataException e) { + LOGGER.info("DataException when trying to load QDN resource", e); + this.deleteWorkingDirectory(); + throw e; } finally { this.postExecute(); @@ -208,8 +228,13 @@ public class ArbitraryDataReader { * serve a cached version of the resource for subsequent requests. * @throws IOException */ - private void deleteWorkingDirectory() throws IOException { - FilesystemUtils.safeDeleteDirectory(this.workingPath, true); + private void deleteWorkingDirectory() { + try { + FilesystemUtils.safeDeleteDirectory(this.workingPath, true); + } catch (IOException e) { + // Ignore failures as this isn't an essential step + LOGGER.info("Unable to delete working path {}: {}", this.workingPath, e.getMessage()); + } } private void createUncompressedDirectory() throws DataException { @@ -408,6 +433,7 @@ public class ArbitraryDataReader { this.decryptUsingAlgo("AES/CBC/PKCS5Padding"); } catch (DataException e) { + LOGGER.info("Unable to decrypt using specific parameters: {}", e.getMessage()); // Something went wrong, so fall back to default AES params (necessary for legacy resource support) this.decryptUsingAlgo("AES"); @@ -420,8 +446,9 @@ public class ArbitraryDataReader { byte[] secret = this.secret58 != null ? Base58.decode(this.secret58) : null; if (secret != null && secret.length == Transformer.AES256_LENGTH) { try { + LOGGER.debug("Decrypting {} using algorithm {}...", this.arbitraryDataResource, algorithm); Path unencryptedPath = Paths.get(this.workingPath.toString(), "zipped.zip"); - SecretKey aesKey = new SecretKeySpec(secret, 0, secret.length, algorithm); + SecretKey aesKey = new SecretKeySpec(secret, 0, secret.length, "AES"); AES.decryptFile(algorithm, aesKey, this.filePath.toString(), unencryptedPath.toString()); // Replace filePath pointer with the encrypted file path @@ -430,7 +457,8 @@ public class ArbitraryDataReader { } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | IOException | InvalidKeyException e) { - throw new DataException(String.format("Unable to decrypt file at path %s: %s", this.filePath, e.getMessage())); + LOGGER.info(String.format("Exception when decrypting %s using algorithm %s", this.arbitraryDataResource, algorithm), e); + throw new DataException(String.format("Unable to decrypt file at path %s using algorithm %s: %s", this.filePath, algorithm, e.getMessage())); } } else { // Assume it is unencrypted. This will be the case when we have built a custom path by combining @@ -477,7 +505,12 @@ public class ArbitraryDataReader { // Delete original compressed file if (FilesystemUtils.pathInsideDataOrTempPath(this.filePath)) { if (Files.exists(this.filePath)) { - Files.delete(this.filePath); + try { + Files.delete(this.filePath); + } catch (IOException e) { + // Ignore failures as this isn't an essential step + LOGGER.info("Unable to delete file at path {}", this.filePath); + } } } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java index 616c9b03..2720e4b2 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java @@ -3,6 +3,7 @@ package org.qortal.arbitrary; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.arbitrary.ArbitraryDataFile.ResourceIdType; +import org.qortal.arbitrary.exception.DataNotPublishedException; import org.qortal.arbitrary.metadata.ArbitraryDataTransactionMetadata; import org.qortal.arbitrary.misc.Service; import org.qortal.controller.arbitrary.ArbitraryDataBuildManager; @@ -325,7 +326,7 @@ public class ArbitraryDataResource { if (latestPut == null) { String message = String.format("Couldn't find PUT transaction for name %s, service %s and identifier %s", this.resourceId, this.service, this.identifierString()); - throw new DataException(message); + throw new DataNotPublishedException(message); } this.latestPutTransaction = latestPut; diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java index 33802d4f..8b1d00c3 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java @@ -23,16 +23,13 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.nio.file.*; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class ArbitraryDataWriter { @@ -50,6 +47,7 @@ public class ArbitraryDataWriter { private final String description; private final List tags; private final Category category; + private List files; private int chunkSize = ArbitraryDataFile.CHUNK_SIZE; @@ -80,12 +78,14 @@ public class ArbitraryDataWriter { this.description = ArbitraryDataTransactionMetadata.limitDescription(description); this.tags = ArbitraryDataTransactionMetadata.limitTags(tags); this.category = category; + this.files = new ArrayList<>(); // Populated in buildFileList() } public void save() throws IOException, DataException, InterruptedException, MissingDataException { try { this.preExecute(); this.validateService(); + this.buildFileList(); this.process(); this.compress(); this.encrypt(); @@ -143,6 +143,24 @@ public class ArbitraryDataWriter { } } + private void buildFileList() throws IOException { + // Single file resources consist of a single element in the file list + boolean isSingleFile = this.filePath.toFile().isFile(); + if (isSingleFile) { + this.files.add(this.filePath.getFileName().toString()); + return; + } + + // Multi file resources require a walk through the directory tree + try (Stream stream = Files.walk(this.filePath)) { + this.files = stream + .filter(Files::isRegularFile) + .map(p -> this.filePath.relativize(p).toString()) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + } + private void process() throws DataException, IOException, MissingDataException { switch (this.method) { @@ -285,6 +303,7 @@ public class ArbitraryDataWriter { metadata.setTags(this.tags); metadata.setCategory(this.category); metadata.setChunks(this.arbitraryDataFile.chunkHashList()); + metadata.setFiles(this.files); metadata.write(); // Create an ArbitraryDataFile from the JSON file (we don't have a signature yet) diff --git a/src/main/java/org/qortal/arbitrary/exception/DataNotPublishedException.java b/src/main/java/org/qortal/arbitrary/exception/DataNotPublishedException.java new file mode 100644 index 00000000..4782826b --- /dev/null +++ b/src/main/java/org/qortal/arbitrary/exception/DataNotPublishedException.java @@ -0,0 +1,22 @@ +package org.qortal.arbitrary.exception; + +import org.qortal.repository.DataException; + +public class DataNotPublishedException extends DataException { + + public DataNotPublishedException() { + } + + public DataNotPublishedException(String message) { + super(message); + } + + public DataNotPublishedException(String message, Throwable cause) { + super(message, cause); + } + + public DataNotPublishedException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataTransactionMetadata.java b/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataTransactionMetadata.java index 0f8b676b..33da343c 100644 --- a/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataTransactionMetadata.java +++ b/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataTransactionMetadata.java @@ -19,6 +19,7 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata { private String description; private List tags; private Category category; + private List files; private static int MAX_TITLE_LENGTH = 80; private static int MAX_DESCRIPTION_LENGTH = 500; @@ -77,6 +78,20 @@ public class ArbitraryDataTransactionMetadata extends ArbitraryDataMetadata { } this.chunks = chunksList; } + + List filesList = new ArrayList<>(); + if (metadata.has("files")) { + JSONArray files = metadata.getJSONArray("files"); + if (files != null) { + for (int i=0; i files) { + this.files = files; + } + + public List getFiles() { + return this.files; + } + public boolean containsChunk(byte[] chunk) { for (byte[] c : this.chunks) { if (Arrays.equals(c, chunk)) { diff --git a/src/main/java/org/qortal/arbitrary/misc/Service.java b/src/main/java/org/qortal/arbitrary/misc/Service.java index 5d94d806..01419d2f 100644 --- a/src/main/java/org/qortal/arbitrary/misc/Service.java +++ b/src/main/java/org/qortal/arbitrary/misc/Service.java @@ -1,16 +1,16 @@ package org.qortal.arbitrary.misc; +import org.apache.commons.io.FilenameUtils; import org.json.JSONObject; import org.qortal.arbitrary.ArbitraryDataRenderer; import org.qortal.transaction.Transaction; import org.qortal.utils.FilesystemUtils; +import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toMap; @@ -18,9 +18,52 @@ import static java.util.stream.Collectors.toMap; public enum Service { AUTO_UPDATE(1, false, null, null), ARBITRARY_DATA(100, false, null, null), + QCHAT_ATTACHMENT(120, true, 1024*1024L, null) { + @Override + public ValidationResult validate(Path path) throws IOException { + ValidationResult superclassResult = super.validate(path); + if (superclassResult != ValidationResult.OK) { + return superclassResult; + } + + // Custom validation function to require a single file, with a whitelisted extension + int fileCount = 0; + File[] files = path.toFile().listFiles(); + // If already a single file, replace the list with one that contains that file only + if (files == null && path.toFile().isFile()) { + files = new File[] { path.toFile() }; + } + if (files != null) { + for (File file : files) { + if (file.getName().equals(".qortal")) { + continue; + } + if (file.isDirectory()) { + return ValidationResult.DIRECTORIES_NOT_ALLOWED; + } + final String extension = FilenameUtils.getExtension(file.getName()).toLowerCase(); + // We must allow blank file extensions because these are used by data published from a plaintext or base64-encoded string + final List allowedExtensions = Arrays.asList("zip", "pdf", "txt", "odt", "ods", "doc", "docx", "xls", "xlsx", "ppt", "pptx", ""); + if (extension == null || !allowedExtensions.contains(extension)) { + return ValidationResult.INVALID_FILE_EXTENSION; + } + fileCount++; + } + } + if (fileCount != 1) { + return ValidationResult.INVALID_FILE_COUNT; + } + return ValidationResult.OK; + } + }, WEBSITE(200, true, null, null) { @Override - public ValidationResult validate(Path path) { + public ValidationResult validate(Path path) throws IOException { + ValidationResult superclassResult = super.validate(path); + if (superclassResult != ValidationResult.OK) { + return superclassResult; + } + // Custom validation function to require an index HTML file in the root directory List fileNames = ArbitraryDataRenderer.indexFiles(); String[] files = path.toFile().list(); @@ -38,6 +81,7 @@ public enum Service { GIT_REPOSITORY(300, false, null, null), IMAGE(400, true, 10*1024*1024L, null), THUMBNAIL(410, true, 500*1024L, null), + QCHAT_IMAGE(420, true, 500*1024L, null), VIDEO(500, false, null, null), AUDIO(600, false, null, null), BLOG(700, false, null, null), @@ -48,7 +92,42 @@ public enum Service { PLAYLIST(910, true, null, null), APP(1000, false, null, null), METADATA(1100, false, null, null), - QORTAL_METADATA(1111, true, 10*1024L, Arrays.asList("title", "description", "tags")); + GIF_REPOSITORY(1200, true, 25*1024*1024L, null) { + @Override + public ValidationResult validate(Path path) throws IOException { + ValidationResult superclassResult = super.validate(path); + if (superclassResult != ValidationResult.OK) { + return superclassResult; + } + + // Custom validation function to require .gif files only, and at least 1 + int gifCount = 0; + File[] files = path.toFile().listFiles(); + // If already a single file, replace the list with one that contains that file only + if (files == null && path.toFile().isFile()) { + files = new File[] { path.toFile() }; + } + if (files != null) { + for (File file : files) { + if (file.getName().equals(".qortal")) { + continue; + } + if (file.isDirectory()) { + return ValidationResult.DIRECTORIES_NOT_ALLOWED; + } + String extension = FilenameUtils.getExtension(file.getName()).toLowerCase(); + if (!Objects.equals(extension, "gif")) { + return ValidationResult.INVALID_FILE_EXTENSION; + } + gifCount++; + } + } + if (gifCount == 0) { + return ValidationResult.MISSING_DATA; + } + return ValidationResult.OK; + } + }; public final int value; private final boolean requiresValidation; @@ -114,7 +193,11 @@ public enum Service { OK(1), MISSING_KEYS(2), EXCEEDS_SIZE_LIMIT(3), - MISSING_INDEX_FILE(4); + MISSING_INDEX_FILE(4), + DIRECTORIES_NOT_ALLOWED(5), + INVALID_FILE_EXTENSION(6), + MISSING_DATA(7), + INVALID_FILE_COUNT(8); public final int value; diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 5fe005d6..3f306b93 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -27,6 +27,7 @@ import org.qortal.block.BlockChain.BlockTimingByHeight; import org.qortal.block.BlockChain.AccountLevelShareBin; import org.qortal.controller.OnlineAccountsManager; import org.qortal.crypto.Crypto; +import org.qortal.crypto.Qortal25519Extras; import org.qortal.data.account.AccountBalanceData; import org.qortal.data.account.AccountData; import org.qortal.data.account.EligibleQoraHolderData; @@ -88,7 +89,8 @@ public class Block { ONLINE_ACCOUNT_UNKNOWN(71), ONLINE_ACCOUNT_SIGNATURES_MISSING(72), ONLINE_ACCOUNT_SIGNATURES_MALFORMED(73), - ONLINE_ACCOUNT_SIGNATURE_INCORRECT(74); + ONLINE_ACCOUNT_SIGNATURE_INCORRECT(74), + ONLINE_ACCOUNT_NONCE_INCORRECT(75); public final int value; @@ -134,7 +136,7 @@ public class Block { } /** Lazy-instantiated expanded info on block's online accounts. */ - private static class ExpandedAccount { + public static class ExpandedAccount { private final RewardShareData rewardShareData; private final int sharePercent; private final boolean isRecipientAlsoMinter; @@ -167,6 +169,13 @@ public class Block { } } + public Account getMintingAccount() { + return this.mintingAccount; + } + public Account getRecipientAccount() { + return this.recipientAccount; + } + /** * Returns share bin for expanded account. *

@@ -183,8 +192,11 @@ public class Block { if (accountLevel <= 0) return null; // level 0 isn't included in any share bins + // Select the correct set of share bins based on block height final BlockChain blockChain = BlockChain.getInstance(); - final AccountLevelShareBin[] shareBinsByLevel = blockChain.getShareBinsByAccountLevel(); + final AccountLevelShareBin[] shareBinsByLevel = (blockHeight >= blockChain.getSharesByLevelV2Height()) ? + blockChain.getShareBinsByAccountLevelV2() : blockChain.getShareBinsByAccountLevelV1(); + if (accountLevel > shareBinsByLevel.length) return null; @@ -197,6 +209,11 @@ public class Block { } + public boolean hasShareBin(AccountLevelShareBin shareBin, int blockHeight) { + AccountLevelShareBin ourShareBin = this.getShareBin(blockHeight); + return ourShareBin != null && shareBin.id == ourShareBin.id; + } + public long distribute(long accountAmount, Map balanceChanges) { if (this.isRecipientAlsoMinter) { // minter & recipient the same - simpler case @@ -221,11 +238,10 @@ public class Block { return accountAmount; } } + /** Always use getExpandedAccounts() to access this, as it's lazy-instantiated. */ private List cachedExpandedAccounts = null; - /** Opportunistic cache of this block's valid online accounts. Only created by call to isValid(). */ - private List cachedValidOnlineAccounts = null; /** Opportunistic cache of this block's valid online reward-shares. Only created by call to isValid(). */ private List cachedOnlineRewardShares = null; @@ -347,18 +363,36 @@ public class Block { int version = parentBlock.getNextBlockVersion(); byte[] reference = parentBlockData.getSignature(); - // Fetch our list of online accounts - List onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts(); - if (onlineAccounts.isEmpty()) { - LOGGER.error("No online accounts - not even our own?"); + // Qortal: minter is always a reward-share, so find actual minter and get their effective minting level + int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, minter.getPublicKey()); + if (minterLevel == 0) { + LOGGER.error("Minter effective level returned zero?"); return null; } - // Find newest online accounts timestamp - long onlineAccountsTimestamp = 0; - for (OnlineAccountData onlineAccountData : onlineAccounts) { - if (onlineAccountData.getTimestamp() > onlineAccountsTimestamp) - onlineAccountsTimestamp = onlineAccountData.getTimestamp(); + int height = parentBlockData.getHeight() + 1; + long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey(), minterLevel); + long onlineAccountsTimestamp = OnlineAccountsManager.getCurrentOnlineAccountTimestamp(); + + // Fetch our list of online accounts, removing any that are missing a nonce + List onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts(onlineAccountsTimestamp); + onlineAccounts.removeIf(a -> a.getNonce() == null || a.getNonce() < 0); + + // After feature trigger, remove any online accounts that are level 0 + if (height >= BlockChain.getInstance().getOnlineAccountMinterLevelValidationHeight()) { + onlineAccounts.removeIf(a -> { + try { + return Account.getRewardShareEffectiveMintingLevel(repository, a.getPublicKey()) == 0; + } catch (DataException e) { + // Something went wrong, so remove the account + return true; + } + }); + } + + if (onlineAccounts.isEmpty()) { + LOGGER.debug("No online accounts - not even our own?"); + return null; } // Load sorted list of reward share public keys into memory, so that the indexes can be obtained. @@ -369,10 +403,6 @@ public class Block { // Map using index into sorted list of reward-shares as key Map indexedOnlineAccounts = new HashMap<>(); for (OnlineAccountData onlineAccountData : onlineAccounts) { - // Disregard online accounts with different timestamps - if (onlineAccountData.getTimestamp() != onlineAccountsTimestamp) - continue; - Integer accountIndex = getRewardShareIndex(onlineAccountData.getPublicKey(), allRewardSharePublicKeys); if (accountIndex == null) // Online account (reward-share) with current timestamp but reward-share cancelled @@ -389,29 +419,43 @@ public class Block { byte[] encodedOnlineAccounts = BlockTransformer.encodeOnlineAccounts(onlineAccountsSet); int onlineAccountsCount = onlineAccountsSet.size(); - // Concatenate online account timestamp signatures (in correct order) - byte[] onlineAccountsSignatures = new byte[onlineAccountsCount * Transformer.SIGNATURE_LENGTH]; - for (int i = 0; i < onlineAccountsCount; ++i) { - Integer accountIndex = accountIndexes.get(i); - OnlineAccountData onlineAccountData = indexedOnlineAccounts.get(accountIndex); - System.arraycopy(onlineAccountData.getSignature(), 0, onlineAccountsSignatures, i * Transformer.SIGNATURE_LENGTH, Transformer.SIGNATURE_LENGTH); + // Collate all signatures + Collection signaturesToAggregate = indexedOnlineAccounts.values() + .stream() + .map(OnlineAccountData::getSignature) + .collect(Collectors.toList()); + + // Aggregated, single signature + byte[] onlineAccountsSignatures = Qortal25519Extras.aggregateSignatures(signaturesToAggregate); + + // Add nonces to the end of the online accounts signatures + try { + // Create ordered list of nonce values + List nonces = new ArrayList<>(); + for (int i = 0; i < onlineAccountsCount; ++i) { + Integer accountIndex = accountIndexes.get(i); + OnlineAccountData onlineAccountData = indexedOnlineAccounts.get(accountIndex); + nonces.add(onlineAccountData.getNonce()); + } + + // Encode the nonces to a byte array + byte[] encodedNonces = BlockTransformer.encodeOnlineAccountNonces(nonces); + + // Append the encoded nonces to the encoded online account signatures + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(onlineAccountsSignatures); + outputStream.write(encodedNonces); + onlineAccountsSignatures = outputStream.toByteArray(); + } + catch (TransformationException | IOException e) { + return null; } byte[] minterSignature = minter.sign(BlockTransformer.getBytesForMinterSignature(parentBlockData, minter.getPublicKey(), encodedOnlineAccounts)); - // Qortal: minter is always a reward-share, so find actual minter and get their effective minting level - int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, minter.getPublicKey()); - if (minterLevel == 0) { - LOGGER.error("Minter effective level returned zero?"); - return null; - } - - long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey(), minterLevel); - int transactionCount = 0; byte[] transactionsSignature = null; - int height = parentBlockData.getHeight() + 1; int atCount = 0; long atFees = 0; @@ -1005,6 +1049,15 @@ public class Block { if (onlineRewardShares == null) return ValidationResult.ONLINE_ACCOUNT_UNKNOWN; + // After feature trigger, require all online account minters to be greater than level 0 + if (this.getBlockData().getHeight() >= BlockChain.getInstance().getOnlineAccountMinterLevelValidationHeight()) { + List expandedAccounts = this.getExpandedAccounts(); + for (ExpandedAccount account : expandedAccounts) { + if (account.getMintingAccount().getEffectiveMintingLevel() == 0) + return ValidationResult.ONLINE_ACCOUNTS_INVALID; + } + } + // If block is past a certain age then we simply assume the signatures were correct long signatureRequirementThreshold = NTP.getTime() - BlockChain.getInstance().getOnlineAccountSignaturesMinLifetime(); if (this.blockData.getTimestamp() < signatureRequirementThreshold) @@ -1013,49 +1066,64 @@ public class Block { if (this.blockData.getOnlineAccountsSignatures() == null || this.blockData.getOnlineAccountsSignatures().length == 0) return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MISSING; - if (this.blockData.getOnlineAccountsSignatures().length != onlineRewardShares.size() * Transformer.SIGNATURE_LENGTH) + final int signaturesLength = Transformer.SIGNATURE_LENGTH; + final int noncesLength = onlineRewardShares.size() * Transformer.INT_LENGTH; + + // We expect nonces to be appended to the online accounts signatures + if (this.blockData.getOnlineAccountsSignatures().length != signaturesLength + noncesLength) return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED; // Check signatures long onlineTimestamp = this.blockData.getOnlineAccountsTimestamp(); byte[] onlineTimestampBytes = Longs.toByteArray(onlineTimestamp); - // If this block is much older than current online timestamp, then there's no point checking current online accounts - List currentOnlineAccounts = onlineTimestamp < NTP.getTime() - OnlineAccountsManager.ONLINE_TIMESTAMP_MODULUS - ? null - : OnlineAccountsManager.getInstance().getOnlineAccounts(); - List latestBlocksOnlineAccounts = OnlineAccountsManager.getInstance().getLatestBlocksOnlineAccounts(); + byte[] encodedOnlineAccountSignatures = this.blockData.getOnlineAccountsSignatures(); - // Extract online accounts' timestamp signatures from block data - List onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(this.blockData.getOnlineAccountsSignatures()); + // Split online account signatures into signature(s) + nonces, then validate the nonces + byte[] extractedSignatures = BlockTransformer.extract(encodedOnlineAccountSignatures, 0, signaturesLength); + byte[] extractedNonces = BlockTransformer.extract(encodedOnlineAccountSignatures, signaturesLength, onlineRewardShares.size() * Transformer.INT_LENGTH); + encodedOnlineAccountSignatures = extractedSignatures; - // We'll build up a list of online accounts to hand over to Controller if block is added to chain - // and this will become latestBlocksOnlineAccounts (above) to reduce CPU load when we process next block... - List ourOnlineAccounts = new ArrayList<>(); + List nonces = BlockTransformer.decodeOnlineAccountNonces(extractedNonces); - for (int i = 0; i < onlineAccountsSignatures.size(); ++i) { - byte[] signature = onlineAccountsSignatures.get(i); + // Build block's view of online accounts (without signatures, as we don't need them here) + Set onlineAccounts = new HashSet<>(); + for (int i = 0; i < onlineRewardShares.size(); ++i) { + Integer nonce = nonces.get(i); byte[] publicKey = onlineRewardShares.get(i).getRewardSharePublicKey(); - OnlineAccountData onlineAccountData = new OnlineAccountData(onlineTimestamp, signature, publicKey); - ourOnlineAccounts.add(onlineAccountData); - - // If signature is still current then no need to perform Ed25519 verify - if (currentOnlineAccounts != null && currentOnlineAccounts.remove(onlineAccountData)) - // remove() returned true, so online account still current - // and one less entry in currentOnlineAccounts to check next time - continue; - - // If signature was okay in latest block then no need to perform Ed25519 verify - if (latestBlocksOnlineAccounts != null && latestBlocksOnlineAccounts.contains(onlineAccountData)) - continue; - - if (!Crypto.verify(publicKey, signature, onlineTimestampBytes)) - return ValidationResult.ONLINE_ACCOUNT_SIGNATURE_INCORRECT; + OnlineAccountData onlineAccountData = new OnlineAccountData(onlineTimestamp, null, publicKey, nonce); + onlineAccounts.add(onlineAccountData); } + // Remove those already validated & cached by online accounts manager - no need to re-validate them + OnlineAccountsManager.getInstance().removeKnown(onlineAccounts, onlineTimestamp); + + // Validate the rest + for (OnlineAccountData onlineAccount : onlineAccounts) + if (!OnlineAccountsManager.getInstance().verifyMemoryPoW(onlineAccount, null)) + return ValidationResult.ONLINE_ACCOUNT_NONCE_INCORRECT; + + // Cache the valid online accounts as they will likely be needed for the next block + OnlineAccountsManager.getInstance().addBlocksOnlineAccounts(onlineAccounts, onlineTimestamp); + + // Extract online accounts' timestamp signatures from block data. Only one signature if aggregated. + List onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(encodedOnlineAccountSignatures); + + // Aggregate all public keys + Collection publicKeys = onlineRewardShares.stream() + .map(RewardShareData::getRewardSharePublicKey) + .collect(Collectors.toList()); + + byte[] aggregatePublicKey = Qortal25519Extras.aggregatePublicKeys(publicKeys); + + byte[] aggregateSignature = onlineAccountsSignatures.get(0); + + // One-step verification of aggregate signature using aggregate public key + if (!Qortal25519Extras.verifyAggregated(aggregatePublicKey, aggregateSignature, onlineTimestampBytes)) + return ValidationResult.ONLINE_ACCOUNT_SIGNATURE_INCORRECT; + // All online accounts valid, so save our list of online accounts for potential later use - this.cachedValidOnlineAccounts = ourOnlineAccounts; this.cachedOnlineRewardShares = onlineRewardShares; return ValidationResult.OK; @@ -1202,6 +1270,7 @@ public class Block { } } } catch (DataException e) { + LOGGER.info("DataException during transaction validation", e); return ValidationResult.TRANSACTION_INVALID; } finally { // Rollback repository changes made by test-processing transactions above @@ -1394,6 +1463,9 @@ public class Block { if (this.blockData.getHeight() == 212937) // Apply fix for block 212937 Block212937.processFix(this); + + else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) + SelfSponsorshipAlgoV1Block.processAccountPenalties(this); } // We're about to (test-)process a batch of transactions, @@ -1426,9 +1498,6 @@ public class Block { postBlockTidy(); - // Give Controller our cached, valid online accounts data (if any) to help reduce CPU load for next block - OnlineAccountsManager.getInstance().pushLatestBlocksOnlineAccounts(this.cachedValidOnlineAccounts); - // Log some debugging info relating to the block weight calculation this.logDebugInfo(); } @@ -1453,19 +1522,23 @@ public class Block { // Batch update in repository repository.getAccountRepository().modifyMintedBlockCounts(allUniqueExpandedAccounts.stream().map(AccountData::getAddress).collect(Collectors.toList()), +1); + // Keep track of level bumps in case we need to apply to other entries + Map bumpedAccounts = new HashMap<>(); + // Local changes and also checks for level bump for (AccountData accountData : allUniqueExpandedAccounts) { // Adjust count locally (in Java) accountData.setBlocksMinted(accountData.getBlocksMinted() + 1); LOGGER.trace(() -> String.format("Block minter %s up to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : ""))); - final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment(); + final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment() + accountData.getBlocksMintedPenalty(); for (int newLevel = maximumLevel; newLevel >= 0; --newLevel) if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) { if (newLevel > accountData.getLevel()) { // Account has increased in level! accountData.setLevel(newLevel); + bumpedAccounts.put(accountData.getAddress(), newLevel); repository.getAccountRepository().setLevel(accountData); LOGGER.trace(() -> String.format("Block minter %s bumped to level %d", accountData.getAddress(), accountData.getLevel())); } @@ -1473,6 +1546,25 @@ public class Block { break; } } + + // Also bump other entries if need be + if (!bumpedAccounts.isEmpty()) { + for (ExpandedAccount expandedAccount : expandedAccounts) { + Integer newLevel = bumpedAccounts.get(expandedAccount.mintingAccountData.getAddress()); + if (newLevel != null && expandedAccount.mintingAccountData.getLevel() != newLevel) { + expandedAccount.mintingAccountData.setLevel(newLevel); + LOGGER.trace("Also bumped {} to level {}", expandedAccount.mintingAccountData.getAddress(), newLevel); + } + + if (!expandedAccount.isRecipientAlsoMinter) { + newLevel = bumpedAccounts.get(expandedAccount.recipientAccountData.getAddress()); + if (newLevel != null && expandedAccount.recipientAccountData.getLevel() != newLevel) { + expandedAccount.recipientAccountData.setLevel(newLevel); + LOGGER.trace("Also bumped {} to level {}", expandedAccount.recipientAccountData.getAddress(), newLevel); + } + } + } + } } protected void processBlockRewards() throws DataException { @@ -1632,6 +1724,9 @@ public class Block { // Revert fix for block 212937 Block212937.orphanFix(this); + else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) + SelfSponsorshipAlgoV1Block.orphanAccountPenalties(this); + // Block rewards, including transaction fees, removed after transactions undone orphanBlockRewards(); @@ -1644,9 +1739,6 @@ public class Block { this.blockData.setHeight(null); postBlockTidy(); - - // Remove any cached, valid online accounts data from Controller - OnlineAccountsManager.getInstance().popLatestBlocksOnlineAccounts(); } protected void orphanTransactionsFromBlock() throws DataException { @@ -1763,7 +1855,7 @@ public class Block { accountData.setBlocksMinted(accountData.getBlocksMinted() - 1); LOGGER.trace(() -> String.format("Block minter %s down to %d minted block%s", accountData.getAddress(), accountData.getBlocksMinted(), (accountData.getBlocksMinted() != 1 ? "s" : ""))); - final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment(); + final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment() + accountData.getBlocksMintedPenalty(); for (int newLevel = maximumLevel; newLevel >= 0; --newLevel) if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) { @@ -1883,13 +1975,72 @@ public class Block { final List onlineFounderAccounts = expandedAccounts.stream().filter(expandedAccount -> expandedAccount.isMinterFounder).collect(Collectors.toList()); final boolean haveFounders = !onlineFounderAccounts.isEmpty(); + // Select the correct set of share bins based on block height + List accountLevelShareBinsForBlock = (this.blockData.getHeight() >= BlockChain.getInstance().getSharesByLevelV2Height()) ? + BlockChain.getInstance().getAccountLevelShareBinsV2() : BlockChain.getInstance().getAccountLevelShareBinsV1(); + // Determine reward candidates based on account level - List accountLevelShareBins = BlockChain.getInstance().getAccountLevelShareBins(); - for (int binIndex = 0; binIndex < accountLevelShareBins.size(); ++binIndex) { - // Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out. + // This needs a deep copy, so the shares can be modified when tiers aren't activated yet + List accountLevelShareBins = new ArrayList<>(); + for (AccountLevelShareBin accountLevelShareBin : accountLevelShareBinsForBlock) { + accountLevelShareBins.add((AccountLevelShareBin) accountLevelShareBin.clone()); + } + + Map> accountsForShareBin = new HashMap<>(); + + // We might need to combine some share bins if they haven't reached the minimum number of minters yet + for (int binIndex = accountLevelShareBins.size()-1; binIndex >= 0; --binIndex) { AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex); - // Object reference compare is OK as all references are read-only from blockchain config. - List binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.getShareBin(this.blockData.getHeight()) == accountLevelShareBin).collect(Collectors.toList()); + + // Find all accounts in share bin. getShareBin() returns null for minter accounts that are also founders, so they are effectively filtered out. + List binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.hasShareBin(accountLevelShareBin, this.blockData.getHeight())).collect(Collectors.toList()); + // Add any accounts that have been moved down from a higher tier + List existingBinnedAccounts = accountsForShareBin.get(binIndex); + if (existingBinnedAccounts != null) + binnedAccounts.addAll(existingBinnedAccounts); + + // Logic below may only apply to higher levels, and only for share bins with a specific range of online accounts + if (accountLevelShareBin.levels.get(0) < BlockChain.getInstance().getShareBinActivationMinLevel() || + binnedAccounts.isEmpty() || binnedAccounts.size() >= BlockChain.getInstance().getMinAccountsToActivateShareBin()) { + // Add all accounts for this share bin to the accountsForShareBin list + accountsForShareBin.put(binIndex, binnedAccounts); + continue; + } + + // Share bin contains more than one, but less than the minimum number of minters. We treat this share bin + // as not activated yet. In these cases, the rewards and minters are combined and paid out to the previous + // share bin, to prevent a single or handful of accounts receiving the entire rewards for a share bin. + // + // Example: + // + // - Share bin for levels 5 and 6 has 100 minters + // - Share bin for levels 7 and 8 has 10 minters + // + // This is below the minimum of 30, so share bins are reconstructed as follows: + // + // - Share bin for levels 5 and 6 now contains 110 minters + // - Share bin for levels 7 and 8 now contains 0 minters + // - Share bin for levels 5 and 6 now pays out rewards for levels 5, 6, 7, and 8 + // - Share bin for levels 7 and 8 pays zero rewards + // + // This process is iterative, so will combine several tiers if needed. + + // Designate this share bin as empty + accountsForShareBin.put(binIndex, new ArrayList<>()); + + // Move the accounts originally intended for this share bin to the previous one + accountsForShareBin.put(binIndex - 1, binnedAccounts); + + // Move the block reward from this share bin to the previous one + AccountLevelShareBin previousShareBin = accountLevelShareBins.get(binIndex - 1); + previousShareBin.share += accountLevelShareBin.share; + accountLevelShareBin.share = 0L; + } + + // Now loop through (potentially modified) share bins and determine the reward candidates + for (int binIndex = 0; binIndex < accountLevelShareBins.size(); ++binIndex) { + AccountLevelShareBin accountLevelShareBin = accountLevelShareBins.get(binIndex); + List binnedAccounts = accountsForShareBin.get(binIndex); // No online accounts in this bin? Skip to next one if (binnedAccounts.isEmpty()) @@ -1907,7 +2058,7 @@ public class Block { // Fetch list of legacy QORA holders who haven't reached their cap of QORT reward. List qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight()); final boolean haveQoraHolders = !qoraHolders.isEmpty(); - final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare(); + final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(this.blockData.getHeight()); // Perform account-level-based reward scaling if appropriate if (!haveFounders) { diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 76815bdf..b96350e6 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -68,10 +68,17 @@ public class BlockChain { atFindNextTransactionFix, newBlockSigHeight, shareBinFix, + sharesByLevelV2Height, + rewardShareLimitTimestamp, calcChainWeightTimestamp, transactionV5Timestamp, transactionV6Timestamp, - disableReferenceTimestamp + disableReferenceTimestamp, + increaseOnlineAccountsDifficultyTimestamp, + onlineAccountMinterLevelValidationHeight, + selfSponsorshipAlgoV1Height, + feeValidationFixTimestamp, + chatReferenceTimestamp; } // Custom transaction fees @@ -93,6 +100,13 @@ public class BlockChain { /** Whether only one registered name is allowed per account. */ private boolean oneNamePerAccount = false; + /** Checkpoints */ + public static class Checkpoint { + public int height; + public String signature; + } + private List checkpoints; + /** Block rewards by block height */ public static class RewardByHeight { public int height; @@ -102,23 +116,48 @@ public class BlockChain { private List rewardsByHeight; /** Share of block reward/fees by account level */ - public static class AccountLevelShareBin { + public static class AccountLevelShareBin implements Cloneable { + public int id; public List levels; @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) public long share; - } - private List sharesByLevel; - /** Generated lookup of share-bin by account level */ - private AccountLevelShareBin[] shareBinsByLevel; - /** Share of block reward/fees to legacy QORA coin holders */ - @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) - private Long qoraHoldersShare; + public Object clone() { + AccountLevelShareBin shareBinCopy = new AccountLevelShareBin(); + List levelsCopy = new ArrayList<>(); + for (Integer level : this.levels) { + levelsCopy.add(level); + } + shareBinCopy.id = this.id; + shareBinCopy.levels = levelsCopy; + shareBinCopy.share = this.share; + return shareBinCopy; + } + } + private List sharesByLevelV1; + private List sharesByLevelV2; + /** Generated lookup of share-bin by account level */ + private AccountLevelShareBin[] shareBinsByLevelV1; + private AccountLevelShareBin[] shareBinsByLevelV2; + + /** Share of block reward/fees to legacy QORA coin holders, by block height */ + public static class ShareByHeight { + public int height; + @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) + public long share; + } + private List qoraHoldersShareByHeight; /** How many legacy QORA per 1 QORT of block reward. */ @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) private Long qoraPerQortReward; + /** Minimum number of accounts before a share bin is considered activated */ + private int minAccountsToActivateShareBin; + + /** Min level at which share bin activation takes place; lower levels allow less than minAccountsPerShareBin */ + private int shareBinActivationMinLevel; + /** * Number of minted blocks required to reach next level from previous. *

@@ -156,7 +195,7 @@ public class BlockChain { private int minAccountLevelToMint; private int minAccountLevelForBlockSubmissions; private int minAccountLevelToRewardShare; - private int maxRewardSharesPerMintingAccount; + private int maxRewardSharesPerFounderMintingAccount; private int founderEffectiveMintingLevel; /** Minimum time to retain online account signatures (ms) for block validity checks. */ @@ -164,6 +203,20 @@ public class BlockChain { /** Maximum time to retain online account signatures (ms) for block validity checks, to allow for clock variance. */ private long onlineAccountSignaturesMaxLifetime; + /** Feature trigger timestamp for ONLINE_ACCOUNTS_MODULUS time interval increase. Can't use + * featureTriggers because unit tests need to set this value via Reflection. */ + private long onlineAccountsModulusV2Timestamp; + + /** Snapshot timestamp for self sponsorship algo V1 */ + private long selfSponsorshipAlgoV1SnapshotTimestamp; + + /** Max reward shares by block height */ + public static class MaxRewardSharesByTimestamp { + public long timestamp; + public int maxShares; + } + private List maxRewardSharesByTimestamp; + /** Settings relating to CIYAM AT feature. */ public static class CiyamAtSettings { /** Fee per step/op-code executed. */ @@ -312,6 +365,16 @@ public class BlockChain { return this.maxBlockSize; } + // Online accounts + public long getOnlineAccountsModulusV2Timestamp() { + return this.onlineAccountsModulusV2Timestamp; + } + + // Self sponsorship algo + public long getSelfSponsorshipAlgoV1SnapshotTimestamp() { + return this.selfSponsorshipAlgoV1SnapshotTimestamp; + } + /** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */ public boolean getRequireGroupForApproval() { return this.requireGroupForApproval; @@ -325,16 +388,28 @@ public class BlockChain { return this.oneNamePerAccount; } + public List getCheckpoints() { + return this.checkpoints; + } + public List getBlockRewardsByHeight() { return this.rewardsByHeight; } - public List getAccountLevelShareBins() { - return this.sharesByLevel; + public List getAccountLevelShareBinsV1() { + return this.sharesByLevelV1; } - public AccountLevelShareBin[] getShareBinsByAccountLevel() { - return this.shareBinsByLevel; + public List getAccountLevelShareBinsV2() { + return this.sharesByLevelV2; + } + + public AccountLevelShareBin[] getShareBinsByAccountLevelV1() { + return this.shareBinsByLevelV1; + } + + public AccountLevelShareBin[] getShareBinsByAccountLevelV2() { + return this.shareBinsByLevelV2; } public List getBlocksNeededByLevel() { @@ -345,14 +420,18 @@ public class BlockChain { return this.cumulativeBlocksByLevel; } - public long getQoraHoldersShare() { - return this.qoraHoldersShare; - } - public long getQoraPerQortReward() { return this.qoraPerQortReward; } + public int getMinAccountsToActivateShareBin() { + return this.minAccountsToActivateShareBin; + } + + public int getShareBinActivationMinLevel() { + return this.shareBinActivationMinLevel; + } + public int getMinAccountLevelToMint() { return this.minAccountLevelToMint; } @@ -365,8 +444,8 @@ public class BlockChain { return this.minAccountLevelToRewardShare; } - public int getMaxRewardSharesPerMintingAccount() { - return this.maxRewardSharesPerMintingAccount; + public int getMaxRewardSharesPerFounderMintingAccount() { + return this.maxRewardSharesPerFounderMintingAccount; } public int getFounderEffectiveMintingLevel() { @@ -399,6 +478,14 @@ public class BlockChain { return this.featureTriggers.get(FeatureTrigger.shareBinFix.name()).intValue(); } + public int getSharesByLevelV2Height() { + return this.featureTriggers.get(FeatureTrigger.sharesByLevelV2Height.name()).intValue(); + } + + public long getRewardShareLimitTimestamp() { + return this.featureTriggers.get(FeatureTrigger.rewardShareLimitTimestamp.name()).longValue(); + } + public long getCalcChainWeightTimestamp() { return this.featureTriggers.get(FeatureTrigger.calcChainWeightTimestamp.name()).longValue(); } @@ -415,6 +502,27 @@ public class BlockChain { return this.featureTriggers.get(FeatureTrigger.disableReferenceTimestamp.name()).longValue(); } + public long getIncreaseOnlineAccountsDifficultyTimestamp() { + return this.featureTriggers.get(FeatureTrigger.increaseOnlineAccountsDifficultyTimestamp.name()).longValue(); + } + + public int getSelfSponsorshipAlgoV1Height() { + return this.featureTriggers.get(FeatureTrigger.selfSponsorshipAlgoV1Height.name()).intValue(); + } + + public long getOnlineAccountMinterLevelValidationHeight() { + return this.featureTriggers.get(FeatureTrigger.onlineAccountMinterLevelValidationHeight.name()).intValue(); + } + + public long getFeeValidationFixTimestamp() { + return this.featureTriggers.get(FeatureTrigger.feeValidationFixTimestamp.name()).longValue(); + } + + public long getChatReferenceTimestamp() { + return this.featureTriggers.get(FeatureTrigger.chatReferenceTimestamp.name()).longValue(); + } + + // More complex getters for aspects that change by height or timestamp public long getRewardAtHeight(int ourHeight) { @@ -443,6 +551,23 @@ public class BlockChain { return this.getUnitFee(); } + public int getMaxRewardSharesAtTimestamp(long ourTimestamp) { + for (int i = maxRewardSharesByTimestamp.size() - 1; i >= 0; --i) + if (maxRewardSharesByTimestamp.get(i).timestamp <= ourTimestamp) + return maxRewardSharesByTimestamp.get(i).maxShares; + + return 0; + } + + public long getQoraHoldersShareAtHeight(int ourHeight) { + // Scan through for QORA share at our height + for (int i = qoraHoldersShareByHeight.size() - 1; i >= 0; --i) + if (qoraHoldersShareByHeight.get(i).height <= ourHeight) + return qoraHoldersShareByHeight.get(i).share; + + return 0; + } + /** Validate blockchain config read from JSON */ private void validateConfig() { if (this.genesisInfo == null) @@ -451,11 +576,14 @@ public class BlockChain { if (this.rewardsByHeight == null) Settings.throwValidationError("No \"rewardsByHeight\" entry found in blockchain config"); - if (this.sharesByLevel == null) - Settings.throwValidationError("No \"sharesByLevel\" entry found in blockchain config"); + if (this.sharesByLevelV1 == null) + Settings.throwValidationError("No \"sharesByLevelV1\" entry found in blockchain config"); - if (this.qoraHoldersShare == null) - Settings.throwValidationError("No \"qoraHoldersShare\" entry found in blockchain config"); + if (this.sharesByLevelV2 == null) + Settings.throwValidationError("No \"sharesByLevelV2\" entry found in blockchain config"); + + if (this.qoraHoldersShareByHeight == null) + Settings.throwValidationError("No \"qoraHoldersShareByHeight\" entry found in blockchain config"); if (this.qoraPerQortReward == null) Settings.throwValidationError("No \"qoraPerQortReward\" entry found in blockchain config"); @@ -492,13 +620,22 @@ public class BlockChain { if (!this.featureTriggers.containsKey(featureTrigger.name())) Settings.throwValidationError(String.format("Missing feature trigger \"%s\" in blockchain config", featureTrigger.name())); - // Check block reward share bounds - long totalShare = this.qoraHoldersShare; + // Check block reward share bounds (V1) + long totalShareV1 = this.qoraHoldersShareByHeight.get(0).share; // Add share percents for account-level-based rewards - for (AccountLevelShareBin accountLevelShareBin : this.sharesByLevel) - totalShare += accountLevelShareBin.share; + for (AccountLevelShareBin accountLevelShareBin : this.sharesByLevelV1) + totalShareV1 += accountLevelShareBin.share; - if (totalShare < 0 || totalShare > 1_00000000L) + if (totalShareV1 < 0 || totalShareV1 > 1_00000000L) + Settings.throwValidationError("Total non-founder share out of bounds (0 1_00000000L) Settings.throwValidationError("Total non-founder share out of bounds (0 checkpoints = BlockChain.getInstance().getCheckpoints(); + for (Checkpoint checkpoint : checkpoints) { + BlockData blockData = repository.getBlockRepository().fromHeight(checkpoint.height); + if (blockData == null) { + // Try the archive + blockData = repository.getBlockArchiveRepository().fromHeight(checkpoint.height); + } + if (blockData == null) { + LOGGER.trace("Couldn't find block for height {}", checkpoint.height); + // This is likely due to the block being pruned, so is safe to ignore. + // Continue, as there might be other blocks we can check more definitively. + continue; + } + + byte[] signature = Base58.decode(checkpoint.signature); + if (!Arrays.equals(signature, blockData.getSignature())) { + LOGGER.info("Error: block at height {} with signature {} doesn't match checkpoint sig: {}. Bootstrapping...", checkpoint.height, Base58.encode(blockData.getSignature()), checkpoint.signature); + needsArchiveRebuild = true; + break; + } + LOGGER.info("Block at height {} matches checkpoint signature", blockData.getHeight()); + } + } + } - boolean hasBlocks = (chainTip != null && chainTip.getHeight() > 1); + // Check first block is Genesis Block + if (!isGenesisBlockValid() || needsArchiveRebuild) { + try { + rebuildBlockchain(); - if (isTopOnly && hasBlocks) { - // Top-only mode is enabled and we have blocks, so it's possible that the genesis block has been pruned - // It's best not to validate it, and there's no real need to - } else { - // Check first block is Genesis Block - if (!isGenesisBlockValid() || needsArchiveRebuild) { - try { - rebuildBlockchain(); - - } catch (InterruptedException e) { - throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage())); - } + } catch (InterruptedException e) { + throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage())); } } @@ -586,9 +757,7 @@ public class BlockChain { try (final Repository repository = RepositoryManager.getRepository()) { repository.checkConsistency(); - // Set the number of blocks to validate based on the pruned state of the chain - // If pruned, subtract an extra 10 to allow room for error - int blocksToValidate = (isTopOnly || archiveEnabled) ? Settings.getInstance().getPruneBlockLimit() - 10 : 1440; + int blocksToValidate = Math.min(Settings.getInstance().getPruneBlockLimit() - 10, 1440); int startHeight = Math.max(repository.getBlockRepository().getBlockchainHeight() - blocksToValidate, 1); BlockData detachedBlockData = repository.getBlockRepository().getDetachedBlockSignature(startHeight); diff --git a/src/main/java/org/qortal/block/SelfSponsorshipAlgoV1Block.java b/src/main/java/org/qortal/block/SelfSponsorshipAlgoV1Block.java new file mode 100644 index 00000000..a9a016b6 --- /dev/null +++ b/src/main/java/org/qortal/block/SelfSponsorshipAlgoV1Block.java @@ -0,0 +1,133 @@ +package org.qortal.block; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.account.SelfSponsorshipAlgoV1; +import org.qortal.api.model.AccountPenaltyStats; +import org.qortal.crypto.Crypto; +import org.qortal.data.account.AccountData; +import org.qortal.data.account.AccountPenaltyData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.utils.Base58; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Self Sponsorship AlgoV1 Block + *

+ * Selected block for the initial run on the "self sponsorship detection algorithm" + */ +public final class SelfSponsorshipAlgoV1Block { + + private static final Logger LOGGER = LogManager.getLogger(SelfSponsorshipAlgoV1Block.class); + + + private SelfSponsorshipAlgoV1Block() { + /* Do not instantiate */ + } + + public static void processAccountPenalties(Block block) throws DataException { + LOGGER.info("Running algo for block processing - this will take a while..."); + logPenaltyStats(block.repository); + long startTime = System.currentTimeMillis(); + Set penalties = getAccountPenalties(block.repository, -5000000); + block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties); + long totalTime = System.currentTimeMillis() - startTime; + String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList())); + LOGGER.info("{} penalty addresses processed (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f)); + logPenaltyStats(block.repository); + + int updatedCount = updateAccountLevels(block.repository, penalties); + LOGGER.info("Account levels updated for {} penalty addresses", updatedCount); + } + + public static void orphanAccountPenalties(Block block) throws DataException { + LOGGER.info("Running algo for block orphaning - this will take a while..."); + logPenaltyStats(block.repository); + long startTime = System.currentTimeMillis(); + Set penalties = getAccountPenalties(block.repository, 5000000); + block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties); + long totalTime = System.currentTimeMillis() - startTime; + String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList())); + LOGGER.info("{} penalty addresses orphaned (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f)); + logPenaltyStats(block.repository); + + int updatedCount = updateAccountLevels(block.repository, penalties); + LOGGER.info("Account levels updated for {} penalty addresses", updatedCount); + } + + public static Set getAccountPenalties(Repository repository, int penalty) throws DataException { + final long snapshotTimestamp = BlockChain.getInstance().getSelfSponsorshipAlgoV1SnapshotTimestamp(); + Set penalties = new LinkedHashSet<>(); + List addresses = repository.getTransactionRepository().getConfirmedRewardShareCreatorsExcludingSelfShares(); + for (String address : addresses) { + //System.out.println(String.format("address: %s", address)); + SelfSponsorshipAlgoV1 selfSponsorshipAlgoV1 = new SelfSponsorshipAlgoV1(repository, address, snapshotTimestamp, false); + selfSponsorshipAlgoV1.run(); + //System.out.println(String.format("Penalty addresses: %d", selfSponsorshipAlgoV1.getPenaltyAddresses().size())); + + for (String penaltyAddress : selfSponsorshipAlgoV1.getPenaltyAddresses()) { + penalties.add(new AccountPenaltyData(penaltyAddress, penalty)); + } + } + return penalties; + } + + private static int updateAccountLevels(Repository repository, Set accountPenalties) throws DataException { + final List cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel(); + final int maximumLevel = cumulativeBlocksByLevel.size() - 1; + + int updatedCount = 0; + + for (AccountPenaltyData penaltyData : accountPenalties) { + AccountData accountData = repository.getAccountRepository().getAccount(penaltyData.getAddress()); + final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment() + accountData.getBlocksMintedPenalty(); + + // Shortcut for penalties + if (effectiveBlocksMinted < 0) { + accountData.setLevel(0); + repository.getAccountRepository().setLevel(accountData); + updatedCount++; + LOGGER.trace(() -> String.format("Block minter %s dropped to level %d", accountData.getAddress(), accountData.getLevel())); + continue; + } + + for (int newLevel = maximumLevel; newLevel >= 0; --newLevel) { + if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) { + accountData.setLevel(newLevel); + repository.getAccountRepository().setLevel(accountData); + updatedCount++; + LOGGER.trace(() -> String.format("Block minter %s increased to level %d", accountData.getAddress(), accountData.getLevel())); + break; + } + } + } + + return updatedCount; + } + + private static void logPenaltyStats(Repository repository) { + try { + LOGGER.info(getPenaltyStats(repository)); + + } catch (DataException e) {} + } + + private static AccountPenaltyStats getPenaltyStats(Repository repository) throws DataException { + List accounts = repository.getAccountRepository().getPenaltyAccounts(); + return AccountPenaltyStats.fromAccounts(accounts); + } + + public static String getHash(List penaltyAddresses) { + if (penaltyAddresses == null || penaltyAddresses.isEmpty()) { + return null; + } + Collections.sort(penaltyAddresses); + return Base58.encode(Crypto.digest(StringUtils.join(penaltyAddresses).getBytes(StandardCharsets.UTF_8))); + } + +} diff --git a/src/main/java/org/qortal/controller/AutoUpdate.java b/src/main/java/org/qortal/controller/AutoUpdate.java index f07e82d1..2ec7c94a 100644 --- a/src/main/java/org/qortal/controller/AutoUpdate.java +++ b/src/main/java/org/qortal/controller/AutoUpdate.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -40,6 +41,7 @@ public class AutoUpdate extends Thread { public static final String JAR_FILENAME = "qortal.jar"; public static final String NEW_JAR_FILENAME = "new-" + JAR_FILENAME; + public static final String AGENTLIB_JVM_HOLDER_ARG = "-DQORTAL_agentlib="; private static final Logger LOGGER = LogManager.getLogger(AutoUpdate.class); private static final long CHECK_INTERVAL = 20 * 60 * 1000L; // ms @@ -243,6 +245,11 @@ public class AutoUpdate extends Thread { // JVM arguments javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); + // Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port + javaCmd = javaCmd.stream() + .map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG)) + .collect(Collectors.toList()); + // Remove JNI options as they won't be supported by command-line 'java' // These are typically added by the AdvancedInstaller Java launcher EXE javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf")); @@ -261,10 +268,19 @@ public class AutoUpdate extends Thread { Translator.INSTANCE.translate("SysTray", "APPLYING_UPDATE_AND_RESTARTING"), MessageType.INFO); - new ProcessBuilder(javaCmd).start(); + ProcessBuilder processBuilder = new ProcessBuilder(javaCmd); + + // New process will inherit our stdout and stderr + processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT); + processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT); + + Process process = processBuilder.start(); + + // Nothing to pipe to new process, so close output stream (process's stdin) + process.getOutputStream().close(); return true; // applying update OK - } catch (IOException e) { + } catch (Exception e) { LOGGER.error(String.format("Failed to apply update: %s", e.getMessage())); try { diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java index 9966d6a9..185dd7cd 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -35,6 +35,8 @@ import org.qortal.transaction.Transaction; import org.qortal.utils.Base58; import org.qortal.utils.NTP; +import static org.junit.Assert.assertNotNull; + // Minting new blocks public class BlockMinter extends Thread { @@ -61,13 +63,12 @@ public class BlockMinter extends Thread { public void run() { Thread.currentThread().setName("BlockMinter"); - if (Settings.getInstance().isLite()) { - // Lite nodes do not mint + if (Settings.getInstance().isTopOnly() || Settings.getInstance().isLite()) { + // Top only and lite nodes do not sign blocks return; } - - try (final Repository repository = RepositoryManager.getRepository()) { - if (Settings.getInstance().getWipeUnconfirmedOnStart()) { + if (Settings.getInstance().getWipeUnconfirmedOnStart()) { + try (final Repository repository = RepositoryManager.getRepository()) { // Wipe existing unconfirmed transactions List unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(); @@ -77,356 +78,377 @@ public class BlockMinter extends Thread { } repository.saveChanges(); + } catch (DataException e) { + LOGGER.warn("Repository issue trying to wipe unconfirmed transactions on start-up: {}", e.getMessage()); + // Fall-through to normal behaviour in case we can recover } + } + BlockData previousBlockData = null; + + // Vars to keep track of blocks that were skipped due to chain weight + byte[] parentSignatureForLastLowWeightBlock = null; + Long timeOfLastLowWeightBlock = null; + + List newBlocks = new ArrayList<>(); + + final boolean isSingleNodeTestnet = Settings.getInstance().isSingleNodeTestnet(); + + try (final Repository repository = RepositoryManager.getRepository()) { // Going to need this a lot... BlockRepository blockRepository = repository.getBlockRepository(); - BlockData previousBlockData = null; - - // Vars to keep track of blocks that were skipped due to chain weight - byte[] parentSignatureForLastLowWeightBlock = null; - Long timeOfLastLowWeightBlock = null; - - List newBlocks = new ArrayList<>(); // Flags for tracking change in whether minting is possible, // so we can notify Controller, and further update SysTray, etc. boolean isMintingPossible = false; boolean wasMintingPossible = isMintingPossible; while (running) { - repository.discardChanges(); // Free repository locks, if any - if (isMintingPossible != wasMintingPossible) Controller.getInstance().onMintingPossibleChange(isMintingPossible); wasMintingPossible = isMintingPossible; - // Sleep for a while - Thread.sleep(1000); - - isMintingPossible = false; - - final Long now = NTP.getTime(); - if (now == null) - continue; - - final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp(); - if (minLatestBlockTimestamp == null) - continue; - - // No online accounts? (e.g. during startup) - if (OnlineAccountsManager.getInstance().getOnlineAccounts().isEmpty()) - continue; - - List mintingAccountsData = repository.getAccountRepository().getMintingAccounts(); - // No minting accounts? - if (mintingAccountsData.isEmpty()) - continue; - - // Disregard minting accounts that are no longer valid, e.g. by transfer/loss of founder flag or account level - // Note that minting accounts are actually reward-shares in Qortal - Iterator madi = mintingAccountsData.iterator(); - while (madi.hasNext()) { - MintingAccountData mintingAccountData = madi.next(); - - RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey()); - if (rewardShareData == null) { - // Reward-share doesn't exist - probably cancelled but not yet removed from node's list of minting accounts - madi.remove(); - continue; - } - - Account mintingAccount = new Account(repository, rewardShareData.getMinter()); - if (!mintingAccount.canMint()) { - // Minting-account component of reward-share can no longer mint - disregard - madi.remove(); - continue; - } - - // Optional (non-validated) prevention of block submissions below a defined level. - // This is an unvalidated version of Blockchain.minAccountLevelToMint - // and exists only to reduce block candidates by default. - int level = mintingAccount.getEffectiveMintingLevel(); - if (level < BlockChain.getInstance().getMinAccountLevelForBlockSubmissions()) { - madi.remove(); - continue; - } - } - - // Needs a mutable copy of the unmodifiableList - List peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers()); - BlockData lastBlockData = blockRepository.getLastBlock(); - - // Disregard peers that have "misbehaved" recently - peers.removeIf(Controller.hasMisbehaved); - - // Disregard peers that don't have a recent block, but only if we're not in recovery mode. - // In that mode, we want to allow minting on top of older blocks, to recover stalled networks. - if (Synchronizer.getInstance().getRecoveryMode() == false) - peers.removeIf(Controller.hasNoRecentBlock); - - // Don't mint if we don't have enough up-to-date peers as where would the transactions/consensus come from? - if (peers.size() < Settings.getInstance().getMinBlockchainPeers()) - continue; - - // If we are stuck on an invalid block, we should allow an alternative to be minted - boolean recoverInvalidBlock = false; - if (Synchronizer.getInstance().timeInvalidBlockLastReceived != null) { - // We've had at least one invalid block - long timeSinceLastValidBlock = NTP.getTime() - Synchronizer.getInstance().timeValidBlockLastReceived; - long timeSinceLastInvalidBlock = NTP.getTime() - Synchronizer.getInstance().timeInvalidBlockLastReceived; - if (timeSinceLastValidBlock > INVALID_BLOCK_RECOVERY_TIMEOUT) { - if (timeSinceLastInvalidBlock < INVALID_BLOCK_RECOVERY_TIMEOUT) { - // Last valid block was more than 10 mins ago, but we've had an invalid block since then - // Assume that the chain has stalled because there is no alternative valid candidate - // Enter recovery mode to allow alternative, valid candidates to be minted - recoverInvalidBlock = true; - } - } - } - - // If our latest block isn't recent then we need to synchronize instead of minting, unless we're in recovery mode. - if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp) - if (Synchronizer.getInstance().getRecoveryMode() == false && recoverInvalidBlock == false) - continue; - - // There are enough peers with a recent block and our latest block is recent - // so go ahead and mint a block if possible. - isMintingPossible = true; - - // Check blockchain hasn't changed - if (previousBlockData == null || !Arrays.equals(previousBlockData.getSignature(), lastBlockData.getSignature())) { - previousBlockData = lastBlockData; - newBlocks.clear(); - - // Reduce log timeout - logTimeout = 10 * 1000L; - - // Last low weight block is no longer valid - parentSignatureForLastLowWeightBlock = null; - } - - // Discard accounts we have already built blocks with - mintingAccountsData.removeIf(mintingAccountData -> newBlocks.stream().anyMatch(newBlock -> Arrays.equals(newBlock.getBlockData().getMinterPublicKey(), mintingAccountData.getPublicKey()))); - - // Do we need to build any potential new blocks? - List newBlocksMintingAccounts = mintingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getPrivateKey())).collect(Collectors.toList()); - - // We might need to sit the next block out, if one of our minting accounts signed the previous one - final byte[] previousBlockMinter = previousBlockData.getMinterPublicKey(); - final boolean mintedLastBlock = mintingAccountsData.stream().anyMatch(mintingAccount -> Arrays.equals(mintingAccount.getPublicKey(), previousBlockMinter)); - if (mintedLastBlock) { - LOGGER.trace(String.format("One of our keys signed the last block, so we won't sign the next one")); - continue; - } - - if (parentSignatureForLastLowWeightBlock != null) { - // The last iteration found a higher weight block in the network, so sleep for a while - // to allow is to sync the higher weight chain. We are sleeping here rather than when - // detected as we don't want to hold the blockchain lock open. - LOGGER.debug("Sleeping for 10 seconds..."); - Thread.sleep(10 * 1000L); - } - - for (PrivateKeyAccount mintingAccount : newBlocksMintingAccounts) { - // First block does the AT heavy-lifting - if (newBlocks.isEmpty()) { - Block newBlock = Block.mint(repository, previousBlockData, mintingAccount); - if (newBlock == null) { - // For some reason we can't mint right now - moderatedLog(() -> LOGGER.error("Couldn't build a to-be-minted block")); - continue; - } - - newBlocks.add(newBlock); - } else { - // The blocks for other minters require less effort... - Block newBlock = newBlocks.get(0).remint(mintingAccount); - if (newBlock == null) { - // For some reason we can't mint right now - moderatedLog(() -> LOGGER.error("Couldn't rebuild a to-be-minted block")); - continue; - } - - newBlocks.add(newBlock); - } - } - - // No potential block candidates? - if (newBlocks.isEmpty()) - continue; - - // Make sure we're the only thread modifying the blockchain - ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock(); - if (!blockchainLock.tryLock(30, TimeUnit.SECONDS)) { - LOGGER.debug("Couldn't acquire blockchain lock even after waiting 30 seconds"); - continue; - } - - boolean newBlockMinted = false; - Block newBlock = null; - try { - // Clear repository session state so we have latest view of data + // Free up any repository locks repository.discardChanges(); - // Now that we have blockchain lock, do final check that chain hasn't changed - BlockData latestBlockData = blockRepository.getLastBlock(); - if (!Arrays.equals(lastBlockData.getSignature(), latestBlockData.getSignature())) + // Sleep for a while. + // It's faster on single node testnets, to allow lots of blocks to be minted quickly. + Thread.sleep(isSingleNodeTestnet ? 50 : 1000); + + isMintingPossible = false; + + final Long now = NTP.getTime(); + if (now == null) continue; - List goodBlocks = new ArrayList<>(); - for (Block testBlock : newBlocks) { - // Is new block's timestamp valid yet? - // We do a separate check as some timestamp checks are skipped for testchains - if (testBlock.isTimestampValid() != ValidationResult.OK) - continue; + final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp(); + if (minLatestBlockTimestamp == null) + continue; - testBlock.preProcess(); + // No online accounts for current timestamp? (e.g. during startup) + if (!OnlineAccountsManager.getInstance().hasOnlineAccounts()) + continue; - // Is new block valid yet? (Before adding unconfirmed transactions) - ValidationResult result = testBlock.isValid(); - if (result != ValidationResult.OK) { - moderatedLog(() -> LOGGER.error(String.format("To-be-minted block invalid '%s' before adding transactions?", result.name()))); + List mintingAccountsData = repository.getAccountRepository().getMintingAccounts(); + // No minting accounts? + if (mintingAccountsData.isEmpty()) + continue; + // Disregard minting accounts that are no longer valid, e.g. by transfer/loss of founder flag or account level + // Note that minting accounts are actually reward-shares in Qortal + Iterator madi = mintingAccountsData.iterator(); + while (madi.hasNext()) { + MintingAccountData mintingAccountData = madi.next(); + + RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey()); + if (rewardShareData == null) { + // Reward-share doesn't exist - probably cancelled but not yet removed from node's list of minting accounts + madi.remove(); continue; } - goodBlocks.add(testBlock); - } + Account mintingAccount = new Account(repository, rewardShareData.getMinter()); + if (!mintingAccount.canMint()) { + // Minting-account component of reward-share can no longer mint - disregard + madi.remove(); + continue; + } - if (goodBlocks.isEmpty()) - continue; - - // Pick best block - final int parentHeight = previousBlockData.getHeight(); - final byte[] parentBlockSignature = previousBlockData.getSignature(); - - BigInteger bestWeight = null; - - for (int bi = 0; bi < goodBlocks.size(); ++bi) { - BlockData blockData = goodBlocks.get(bi).getBlockData(); - - BlockSummaryData blockSummaryData = new BlockSummaryData(blockData); - int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, blockData.getMinterPublicKey()); - blockSummaryData.setMinterLevel(minterLevel); - - BigInteger blockWeight = Block.calcBlockWeight(parentHeight, parentBlockSignature, blockSummaryData); - - if (bestWeight == null || blockWeight.compareTo(bestWeight) < 0) { - newBlock = goodBlocks.get(bi); - bestWeight = blockWeight; + // Optional (non-validated) prevention of block submissions below a defined level. + // This is an unvalidated version of Blockchain.minAccountLevelToMint + // and exists only to reduce block candidates by default. + int level = mintingAccount.getEffectiveMintingLevel(); + if (level < BlockChain.getInstance().getMinAccountLevelForBlockSubmissions()) { + madi.remove(); + continue; } } - try { - if (this.higherWeightChainExists(repository, bestWeight)) { + // Needs a mutable copy of the unmodifiableList + List peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers()); + BlockData lastBlockData = blockRepository.getLastBlock(); - // Check if the base block has updated since the last time we were here - if (parentSignatureForLastLowWeightBlock == null || timeOfLastLowWeightBlock == null || - !Arrays.equals(parentSignatureForLastLowWeightBlock, previousBlockData.getSignature())) { - // We've switched to a different chain, so reset the timer - timeOfLastLowWeightBlock = NTP.getTime(); + // Disregard peers that have "misbehaved" recently + peers.removeIf(Controller.hasMisbehaved); + + // Disregard peers that don't have a recent block, but only if we're not in recovery mode. + // In that mode, we want to allow minting on top of older blocks, to recover stalled networks. + if (Synchronizer.getInstance().getRecoveryMode() == false) + peers.removeIf(Controller.hasNoRecentBlock); + + // Don't mint if we don't have enough up-to-date peers as where would the transactions/consensus come from? + if (peers.size() < Settings.getInstance().getMinBlockchainPeers()) + continue; + + // If we are stuck on an invalid block, we should allow an alternative to be minted + boolean recoverInvalidBlock = false; + if (Synchronizer.getInstance().timeInvalidBlockLastReceived != null) { + // We've had at least one invalid block + long timeSinceLastValidBlock = NTP.getTime() - Synchronizer.getInstance().timeValidBlockLastReceived; + long timeSinceLastInvalidBlock = NTP.getTime() - Synchronizer.getInstance().timeInvalidBlockLastReceived; + if (timeSinceLastValidBlock > INVALID_BLOCK_RECOVERY_TIMEOUT) { + if (timeSinceLastInvalidBlock < INVALID_BLOCK_RECOVERY_TIMEOUT) { + // Last valid block was more than 10 mins ago, but we've had an invalid block since then + // Assume that the chain has stalled because there is no alternative valid candidate + // Enter recovery mode to allow alternative, valid candidates to be minted + recoverInvalidBlock = true; } - parentSignatureForLastLowWeightBlock = previousBlockData.getSignature(); + } + } - // If less than 30 seconds has passed since first detection the higher weight chain, - // we should skip our block submission to give us the opportunity to sync to the better chain - if (NTP.getTime() - timeOfLastLowWeightBlock < 30*1000L) { - LOGGER.debug("Higher weight chain found in peers, so not signing a block this round"); - LOGGER.debug("Time since detected: {}ms", NTP.getTime() - timeOfLastLowWeightBlock); + // If our latest block isn't recent then we need to synchronize instead of minting, unless we're in recovery mode. + if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp) + if (Synchronizer.getInstance().getRecoveryMode() == false && recoverInvalidBlock == false) + continue; + + // There are enough peers with a recent block and our latest block is recent + // so go ahead and mint a block if possible. + isMintingPossible = true; + + // Check blockchain hasn't changed + if (previousBlockData == null || !Arrays.equals(previousBlockData.getSignature(), lastBlockData.getSignature())) { + previousBlockData = lastBlockData; + newBlocks.clear(); + + // Reduce log timeout + logTimeout = 10 * 1000L; + + // Last low weight block is no longer valid + parentSignatureForLastLowWeightBlock = null; + } + + // Discard accounts we have already built blocks with + mintingAccountsData.removeIf(mintingAccountData -> newBlocks.stream().anyMatch(newBlock -> Arrays.equals(newBlock.getBlockData().getMinterPublicKey(), mintingAccountData.getPublicKey()))); + + // Do we need to build any potential new blocks? + List newBlocksMintingAccounts = mintingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getPrivateKey())).collect(Collectors.toList()); + + // We might need to sit the next block out, if one of our minting accounts signed the previous one + // Skip this check for single node testnets, since they definitely need to mint every block + byte[] previousBlockMinter = previousBlockData.getMinterPublicKey(); + boolean mintedLastBlock = mintingAccountsData.stream().anyMatch(mintingAccount -> Arrays.equals(mintingAccount.getPublicKey(), previousBlockMinter)); + if (mintedLastBlock && !isSingleNodeTestnet) { + LOGGER.trace(String.format("One of our keys signed the last block, so we won't sign the next one")); + continue; + } + + if (parentSignatureForLastLowWeightBlock != null) { + // The last iteration found a higher weight block in the network, so sleep for a while + // to allow is to sync the higher weight chain. We are sleeping here rather than when + // detected as we don't want to hold the blockchain lock open. + LOGGER.info("Sleeping for 10 seconds..."); + Thread.sleep(10 * 1000L); + } + + for (PrivateKeyAccount mintingAccount : newBlocksMintingAccounts) { + // First block does the AT heavy-lifting + if (newBlocks.isEmpty()) { + Block newBlock = Block.mint(repository, previousBlockData, mintingAccount); + if (newBlock == null) { + // For some reason we can't mint right now + moderatedLog(() -> LOGGER.info("Couldn't build a to-be-minted block")); continue; } - else { - // More than 30 seconds have passed, so we should submit our block candidate anyway. - LOGGER.debug("More than 30 seconds passed, so proceeding to submit block candidate..."); + + newBlocks.add(newBlock); + } else { + // The blocks for other minters require less effort... + Block newBlock = newBlocks.get(0).remint(mintingAccount); + if (newBlock == null) { + // For some reason we can't mint right now + moderatedLog(() -> LOGGER.error("Couldn't rebuild a to-be-minted block")); + continue; } + + newBlocks.add(newBlock); } - else { - LOGGER.debug("No higher weight chain found in peers"); - } - } catch (DataException e) { - LOGGER.debug("Unable to check for a higher weight chain. Proceeding anyway..."); } - // Discard any uncommitted changes as a result of the higher weight chain detection - repository.discardChanges(); + // No potential block candidates? + if (newBlocks.isEmpty()) + continue; - // Clear variables that track low weight blocks - parentSignatureForLastLowWeightBlock = null; - timeOfLastLowWeightBlock = null; - - - // Add unconfirmed transactions - addUnconfirmedTransactions(repository, newBlock); - - // Sign to create block's signature - newBlock.sign(); - - // Is newBlock still valid? - ValidationResult validationResult = newBlock.isValid(); - if (validationResult != ValidationResult.OK) { - // No longer valid? Report and discard - LOGGER.error(String.format("To-be-minted block now invalid '%s' after adding unconfirmed transactions?", validationResult.name())); - - // Rebuild block candidates, just to be sure - newBlocks.clear(); + // Make sure we're the only thread modifying the blockchain + ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock(); + if (!blockchainLock.tryLock(30, TimeUnit.SECONDS)) { + LOGGER.debug("Couldn't acquire blockchain lock even after waiting 30 seconds"); continue; } - // Add to blockchain - something else will notice and broadcast new block to network + boolean newBlockMinted = false; + Block newBlock = null; + try { - newBlock.process(); + // Clear repository session state so we have latest view of data + repository.discardChanges(); - repository.saveChanges(); + // Now that we have blockchain lock, do final check that chain hasn't changed + BlockData latestBlockData = blockRepository.getLastBlock(); + if (!Arrays.equals(lastBlockData.getSignature(), latestBlockData.getSignature())) + continue; - LOGGER.info(String.format("Minted new block: %d", newBlock.getBlockData().getHeight())); + List goodBlocks = new ArrayList<>(); + boolean wasInvalidBlockDiscarded = false; + Iterator newBlocksIterator = newBlocks.iterator(); - RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(newBlock.getBlockData().getMinterPublicKey()); + while (newBlocksIterator.hasNext()) { + Block testBlock = newBlocksIterator.next(); - if (rewardShareData != null) { - LOGGER.info(String.format("Minted block %d, sig %.8s, parent sig: %.8s by %s on behalf of %s", - newBlock.getBlockData().getHeight(), - Base58.encode(newBlock.getBlockData().getSignature()), - Base58.encode(newBlock.getParent().getSignature()), - rewardShareData.getMinter(), - rewardShareData.getRecipient())); - } else { - LOGGER.info(String.format("Minted block %d, sig %.8s, parent sig: %.8s by %s", - newBlock.getBlockData().getHeight(), - Base58.encode(newBlock.getBlockData().getSignature()), - Base58.encode(newBlock.getParent().getSignature()), - newBlock.getMinter().getAddress())); + // Is new block's timestamp valid yet? + // We do a separate check as some timestamp checks are skipped for testchains + if (testBlock.isTimestampValid() != ValidationResult.OK) + continue; + + testBlock.preProcess(); + + // Is new block valid yet? (Before adding unconfirmed transactions) + ValidationResult result = testBlock.isValid(); + if (result != ValidationResult.OK) { + moderatedLog(() -> LOGGER.error(String.format("To-be-minted block invalid '%s' before adding transactions?", result.name()))); + + newBlocksIterator.remove(); + wasInvalidBlockDiscarded = true; + /* + * Bail out fast so that we loop around from the top again. + * This gives BlockMinter the possibility to remint this candidate block using another block from newBlocks, + * via the Blocks.remint() method, which avoids having to re-process Block ATs all over again. + * Particularly useful if some aspect of Blocks changes due a timestamp-based feature-trigger (see BlockChain class). + */ + break; + } + + goodBlocks.add(testBlock); } - // Notify network after we're released blockchain lock - newBlockMinted = true; + if (wasInvalidBlockDiscarded || goodBlocks.isEmpty()) + continue; - // Notify Controller - repository.discardChanges(); // clear transaction status to prevent deadlocks - Controller.getInstance().onNewBlock(newBlock.getBlockData()); - } catch (DataException e) { - // Unable to process block - report and discard - LOGGER.error("Unable to process newly minted block?", e); - newBlocks.clear(); + // Pick best block + final int parentHeight = previousBlockData.getHeight(); + final byte[] parentBlockSignature = previousBlockData.getSignature(); + + BigInteger bestWeight = null; + + for (int bi = 0; bi < goodBlocks.size(); ++bi) { + BlockData blockData = goodBlocks.get(bi).getBlockData(); + + BlockSummaryData blockSummaryData = new BlockSummaryData(blockData); + int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, blockData.getMinterPublicKey()); + blockSummaryData.setMinterLevel(minterLevel); + + BigInteger blockWeight = Block.calcBlockWeight(parentHeight, parentBlockSignature, blockSummaryData); + + if (bestWeight == null || blockWeight.compareTo(bestWeight) < 0) { + newBlock = goodBlocks.get(bi); + bestWeight = blockWeight; + } + } + + try { + if (this.higherWeightChainExists(repository, bestWeight)) { + + // Check if the base block has updated since the last time we were here + if (parentSignatureForLastLowWeightBlock == null || timeOfLastLowWeightBlock == null || + !Arrays.equals(parentSignatureForLastLowWeightBlock, previousBlockData.getSignature())) { + // We've switched to a different chain, so reset the timer + timeOfLastLowWeightBlock = NTP.getTime(); + } + parentSignatureForLastLowWeightBlock = previousBlockData.getSignature(); + + // If less than 30 seconds has passed since first detection the higher weight chain, + // we should skip our block submission to give us the opportunity to sync to the better chain + if (NTP.getTime() - timeOfLastLowWeightBlock < 30 * 1000L) { + LOGGER.info("Higher weight chain found in peers, so not signing a block this round"); + LOGGER.info("Time since detected: {}", NTP.getTime() - timeOfLastLowWeightBlock); + continue; + } else { + // More than 30 seconds have passed, so we should submit our block candidate anyway. + LOGGER.info("More than 30 seconds passed, so proceeding to submit block candidate..."); + } + } else { + LOGGER.debug("No higher weight chain found in peers"); + } + } catch (DataException e) { + LOGGER.debug("Unable to check for a higher weight chain. Proceeding anyway..."); + } + + // Discard any uncommitted changes as a result of the higher weight chain detection + repository.discardChanges(); + + // Clear variables that track low weight blocks + parentSignatureForLastLowWeightBlock = null; + timeOfLastLowWeightBlock = null; + + // Add unconfirmed transactions + addUnconfirmedTransactions(repository, newBlock); + + // Sign to create block's signature + newBlock.sign(); + + // Is newBlock still valid? + ValidationResult validationResult = newBlock.isValid(); + if (validationResult != ValidationResult.OK) { + // No longer valid? Report and discard + LOGGER.error(String.format("To-be-minted block now invalid '%s' after adding unconfirmed transactions?", validationResult.name())); + + // Rebuild block candidates, just to be sure + newBlocks.clear(); + continue; + } + + // Add to blockchain - something else will notice and broadcast new block to network + try { + newBlock.process(); + + repository.saveChanges(); + + LOGGER.info(String.format("Minted new block: %d", newBlock.getBlockData().getHeight())); + + RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(newBlock.getBlockData().getMinterPublicKey()); + + if (rewardShareData != null) { + LOGGER.info(String.format("Minted block %d, sig %.8s, parent sig: %.8s by %s on behalf of %s", + newBlock.getBlockData().getHeight(), + Base58.encode(newBlock.getBlockData().getSignature()), + Base58.encode(newBlock.getParent().getSignature()), + rewardShareData.getMinter(), + rewardShareData.getRecipient())); + } else { + LOGGER.info(String.format("Minted block %d, sig %.8s, parent sig: %.8s by %s", + newBlock.getBlockData().getHeight(), + Base58.encode(newBlock.getBlockData().getSignature()), + Base58.encode(newBlock.getParent().getSignature()), + newBlock.getMinter().getAddress())); + } + + // Notify network after we're released blockchain lock + newBlockMinted = true; + + // Notify Controller + repository.discardChanges(); // clear transaction status to prevent deadlocks + Controller.getInstance().onNewBlock(newBlock.getBlockData()); + } catch (DataException e) { + // Unable to process block - report and discard + LOGGER.error("Unable to process newly minted block?", e); + newBlocks.clear(); + } + } finally { + blockchainLock.unlock(); } - } finally { - blockchainLock.unlock(); - } - if (newBlockMinted) { - // Broadcast our new chain to network - BlockData newBlockData = newBlock.getBlockData(); + if (newBlockMinted) { + // Broadcast our new chain to network + Network.getInstance().broadcastOurChain(); + } - Network network = Network.getInstance(); - network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newBlockData)); + } catch (InterruptedException e) { + // We've been interrupted - time to exit + return; } } } catch (DataException e) { - LOGGER.warn("Repository issue while running block minter", e); - } catch (InterruptedException e) { - // We've been interrupted - time to exit - return; + LOGGER.warn("Repository issue while running block minter - NO LONGER MINTING", e); } } @@ -488,6 +510,21 @@ public class BlockMinter extends Thread { PrivateKeyAccount mintingAccount = mintingAndOnlineAccounts[0]; + Block block = mintTestingBlockRetainingTimestamps(repository, mintingAccount); + assertNotNull("Minted block must not be null", block); + + return block; + } + + public static Block mintTestingBlockUnvalidated(Repository repository, PrivateKeyAccount... mintingAndOnlineAccounts) throws DataException { + if (!BlockChain.getInstance().isTestChain()) + throw new DataException("Ignoring attempt to mint testing block for non-test chain!"); + + // Ensure mintingAccount is 'online' so blocks can be minted + OnlineAccountsManager.getInstance().ensureTestingAccountsOnline(mintingAndOnlineAccounts); + + PrivateKeyAccount mintingAccount = mintingAndOnlineAccounts[0]; + return mintTestingBlockRetainingTimestamps(repository, mintingAccount); } @@ -495,6 +532,8 @@ public class BlockMinter extends Thread { BlockData previousBlockData = repository.getBlockRepository().getLastBlock(); Block newBlock = Block.mint(repository, previousBlockData, mintingAccount); + if (newBlock == null) + return null; // Make sure we're the only thread modifying the blockchain ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock(); @@ -557,18 +596,23 @@ public class BlockMinter extends Thread { // This peer has common block data CommonBlockData commonBlockData = peer.getCommonBlockData(); BlockSummaryData commonBlockSummaryData = commonBlockData.getCommonBlockSummary(); - if (commonBlockData.getChainWeight() != null) { + if (commonBlockData.getChainWeight() != null && peer.getCommonBlockData().getBlockSummariesAfterCommonBlock() != null) { // The synchronizer has calculated this peer's chain weight - BigInteger ourChainWeightSinceCommonBlock = this.getOurChainWeightSinceBlock(repository, commonBlockSummaryData, commonBlockData.getBlockSummariesAfterCommonBlock()); - BigInteger ourChainWeight = ourChainWeightSinceCommonBlock.add(blockCandidateWeight); - BigInteger peerChainWeight = commonBlockData.getChainWeight(); - if (peerChainWeight.compareTo(ourChainWeight) >= 0) { - // This peer has a higher weight chain than ours - LOGGER.debug("Peer {} is on a higher weight chain ({}) than ours ({})", peer, formatter.format(peerChainWeight), formatter.format(ourChainWeight)); - return true; + if (!Synchronizer.getInstance().containsInvalidBlockSummary(peer.getCommonBlockData().getBlockSummariesAfterCommonBlock())) { + // .. and it doesn't hold any invalid blocks + BigInteger ourChainWeightSinceCommonBlock = this.getOurChainWeightSinceBlock(repository, commonBlockSummaryData, commonBlockData.getBlockSummariesAfterCommonBlock()); + BigInteger ourChainWeight = ourChainWeightSinceCommonBlock.add(blockCandidateWeight); + BigInteger peerChainWeight = commonBlockData.getChainWeight(); + if (peerChainWeight.compareTo(ourChainWeight) >= 0) { + // This peer has a higher weight chain than ours + LOGGER.info("Peer {} is on a higher weight chain ({}) than ours ({})", peer, formatter.format(peerChainWeight), formatter.format(ourChainWeight)); + return true; + } else { + LOGGER.debug("Peer {} is on a lower weight chain ({}) than ours ({})", peer, formatter.format(peerChainWeight), formatter.format(ourChainWeight)); + } } else { - LOGGER.debug("Peer {} is on a lower weight chain ({}) than ours ({})", peer, formatter.format(peerChainWeight), formatter.format(ourChainWeight)); + LOGGER.debug("Peer {} has an invalid block", peer); } } else { LOGGER.debug("Peer {} has no chain weight", peer); diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 67bfbf13..e9e1fcc2 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -29,6 +29,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; +import org.qortal.account.Account; import org.qortal.api.ApiService; import org.qortal.api.DomainMapService; import org.qortal.api.GatewayService; @@ -45,7 +46,6 @@ import org.qortal.data.account.AccountData; import org.qortal.data.block.BlockData; import org.qortal.data.block.BlockSummaryData; import org.qortal.data.naming.NameData; -import org.qortal.data.network.PeerChainTipData; import org.qortal.data.network.PeerData; import org.qortal.data.transaction.ChatTransactionData; import org.qortal.data.transaction.TransactionData; @@ -113,6 +113,7 @@ public class Controller extends Thread { private long repositoryBackupTimestamp = startTime; // ms private long repositoryMaintenanceTimestamp = startTime; // ms private long repositoryCheckpointTimestamp = startTime; // ms + private long prunePeersTimestamp = startTime; // ms private long ntpCheckTimestamp = startTime; // ms private long deleteExpiredTimestamp = startTime + DELETE_EXPIRED_INTERVAL; // ms @@ -316,6 +317,10 @@ public class Controller extends Thread { } } + public static long uptime() { + return System.currentTimeMillis() - Controller.startTime; + } + /** Returns highest block, or null if it's not available. */ public BlockData getChainTip() { synchronized (this.latestBlocks) { @@ -496,6 +501,9 @@ public class Controller extends Thread { AutoUpdate.getInstance().start(); } + LOGGER.info("Starting wallets"); + PirateChainWalletController.getInstance().start(); + LOGGER.info(String.format("Starting API on port %d", Settings.getInstance().getApiPort())); try { ApiService apiService = ApiService.getInstance(); @@ -552,6 +560,7 @@ public class Controller extends Thread { final long repositoryBackupInterval = Settings.getInstance().getRepositoryBackupInterval(); final long repositoryCheckpointInterval = Settings.getInstance().getRepositoryCheckpointInterval(); long repositoryMaintenanceInterval = getRandomRepositoryMaintenanceInterval(); + final long prunePeersInterval = 5 * 60 * 1000L; // Every 5 minutes // Start executor service for trimming or pruning PruneManager.getInstance().start(); @@ -649,10 +658,15 @@ public class Controller extends Thread { } // Prune stuck/slow/old peers - try { - Network.getInstance().prunePeers(); - } catch (DataException e) { - LOGGER.warn(String.format("Repository issue when trying to prune peers: %s", e.getMessage())); + if (now >= prunePeersTimestamp + prunePeersInterval) { + prunePeersTimestamp = now + prunePeersInterval; + + try { + LOGGER.debug("Pruning peers..."); + Network.getInstance().prunePeers(); + } catch (DataException e) { + LOGGER.warn(String.format("Repository issue when trying to prune peers: %s", e.getMessage())); + } } // Delete expired transactions @@ -717,25 +731,25 @@ public class Controller extends Thread { public static final Predicate hasNoRecentBlock = peer -> { final Long minLatestBlockTimestamp = getMinimumLatestBlockTimestamp(); - final PeerChainTipData peerChainTipData = peer.getChainTipData(); - return peerChainTipData == null || peerChainTipData.getLastBlockTimestamp() == null || peerChainTipData.getLastBlockTimestamp() < minLatestBlockTimestamp; + final BlockSummaryData peerChainTipData = peer.getChainTipData(); + return peerChainTipData == null || peerChainTipData.getTimestamp() == null || peerChainTipData.getTimestamp() < minLatestBlockTimestamp; }; public static final Predicate hasNoOrSameBlock = peer -> { final BlockData latestBlockData = getInstance().getChainTip(); - final PeerChainTipData peerChainTipData = peer.getChainTipData(); - return peerChainTipData == null || peerChainTipData.getLastBlockSignature() == null || Arrays.equals(latestBlockData.getSignature(), peerChainTipData.getLastBlockSignature()); + final BlockSummaryData peerChainTipData = peer.getChainTipData(); + return peerChainTipData == null || peerChainTipData.getSignature() == null || Arrays.equals(latestBlockData.getSignature(), peerChainTipData.getSignature()); }; public static final Predicate hasOnlyGenesisBlock = peer -> { - final PeerChainTipData peerChainTipData = peer.getChainTipData(); - return peerChainTipData == null || peerChainTipData.getLastHeight() == null || peerChainTipData.getLastHeight() == 1; + final BlockSummaryData peerChainTipData = peer.getChainTipData(); + return peerChainTipData == null || peerChainTipData.getHeight() == 1; }; public static final Predicate hasInferiorChainTip = peer -> { - final PeerChainTipData peerChainTipData = peer.getChainTipData(); + final BlockSummaryData peerChainTipData = peer.getChainTipData(); final List inferiorChainTips = Synchronizer.getInstance().inferiorChainSignatures; - return peerChainTipData == null || peerChainTipData.getLastBlockSignature() == null || inferiorChainTips.contains(ByteArray.wrap(peerChainTipData.getLastBlockSignature())); + return peerChainTipData == null || peerChainTipData.getSignature() == null || inferiorChainTips.contains(ByteArray.wrap(peerChainTipData.getSignature())); }; public static final Predicate hasOldVersion = peer -> { @@ -743,6 +757,28 @@ public class Controller extends Thread { return peer.isAtLeastVersion(minPeerVersion) == false; }; + public static final Predicate hasInvalidSigner = peer -> { + final BlockSummaryData peerChainTipData = peer.getChainTipData(); + if (peerChainTipData == null) + return true; + + try (Repository repository = RepositoryManager.getRepository()) { + return Account.getRewardShareEffectiveMintingLevel(repository, peerChainTipData.getMinterPublicKey()) == 0; + } catch (DataException e) { + return true; + } + }; + + public static final Predicate wasRecentlyTooDivergent = peer -> { + Long now = NTP.getTime(); + Long peerLastTooDivergentTime = peer.getLastTooDivergentTime(); + if (now == null || peerLastTooDivergentTime == null) + return false; + + // Exclude any peers that were TOO_DIVERGENT in the last 5 mins + return (now - peerLastTooDivergentTime < 5 * 60 * 1000L); + }; + private long getRandomRepositoryMaintenanceInterval() { final long minInterval = Settings.getInstance().getRepositoryMaintenanceMinInterval(); final long maxInterval = Settings.getInstance().getRepositoryMaintenanceMaxInterval(); @@ -812,7 +848,7 @@ public class Controller extends Thread { actionText = String.format("%s", Translator.INSTANCE.translate("SysTray", "SYNCHRONIZING_BLOCKCHAIN")); SysTray.getInstance().setTrayIcon(3); } - else if (OnlineAccountsManager.getInstance().hasOnlineAccounts()) { + else if (OnlineAccountsManager.getInstance().hasActiveOnlineAccountSignatures()) { actionText = Translator.INSTANCE.translate("SysTray", "MINTING_ENABLED"); SysTray.getInstance().setTrayIcon(2); } @@ -825,6 +861,12 @@ public class Controller extends Thread { String tooltip = String.format("%s - %d %s", actionText, numberOfPeers, connectionsText); if (!Settings.getInstance().isLite()) { tooltip = tooltip.concat(String.format(" - %s %d", heightText, height)); + + final Integer blocksRemaining = Synchronizer.getInstance().getBlocksRemaining(); + if (blocksRemaining != null && blocksRemaining > 0) { + String blocksRemainingText = Translator.INSTANCE.translate("SysTray", "BLOCKS_REMAINING"); + tooltip = tooltip.concat(String.format(" - %d %s", blocksRemaining, blocksRemainingText)); + } } tooltip = tooltip.concat(String.format("\n%s: %s", Translator.INSTANCE.translate("SysTray", "BUILD_VERSION"), this.buildVersion)); SysTray.getInstance().setToolTipText(tooltip); @@ -883,6 +925,9 @@ public class Controller extends Thread { LOGGER.info("Shutting down API"); ApiService.getInstance().stop(); + LOGGER.info("Shutting down wallets"); + PirateChainWalletController.getInstance().shutdown(); + if (Settings.getInstance().isAutoUpdateEnabled()) { LOGGER.info("Shutting down auto-update"); AutoUpdate.getInstance().shutdown(); @@ -994,8 +1039,7 @@ public class Controller extends Thread { network.broadcast(peer -> peer.isOutbound() ? network.buildPeersMessage(peer) : new GetPeersMessage()); // Send our current height - BlockData latestBlockData = getChainTip(); - network.broadcast(peer -> network.buildHeightMessage(peer, latestBlockData)); + network.broadcastOurChain(); // Request unconfirmed transaction signatures, but only if we're up-to-date. // If we're NOT up-to-date then priority is synchronizing first @@ -1202,6 +1246,10 @@ public class Controller extends Thread { onNetworkHeightV2Message(peer, message); break; + case BLOCK_SUMMARIES_V2: + onNetworkBlockSummariesV2Message(peer, message); + break; + case GET_TRANSACTION: TransactionImporter.getInstance().onNetworkGetTransactionMessage(peer, message); break; @@ -1219,19 +1267,18 @@ public class Controller extends Thread { break; case GET_ONLINE_ACCOUNTS: - OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsMessage(peer, message); - break; - case ONLINE_ACCOUNTS: - OnlineAccountsManager.getInstance().onNetworkOnlineAccountsMessage(peer, message); - break; - case GET_ONLINE_ACCOUNTS_V2: - OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsV2Message(peer, message); + case ONLINE_ACCOUNTS_V2: + // No longer supported - to be eventually removed break; - case ONLINE_ACCOUNTS_V2: - OnlineAccountsManager.getInstance().onNetworkOnlineAccountsV2Message(peer, message); + case GET_ONLINE_ACCOUNTS_V3: + OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsV3Message(peer, message); + break; + + case ONLINE_ACCOUNTS_V3: + OnlineAccountsManager.getInstance().onNetworkOnlineAccountsV3Message(peer, message); break; case GET_ARBITRARY_DATA: @@ -1357,8 +1404,10 @@ public class Controller extends Thread { // Send valid, yet unexpected message type in response, so peer's synchronizer doesn't have to wait for timeout LOGGER.debug(() -> String.format("Sending 'block unknown' response to peer %s for GET_BLOCK request for unknown block %s", peer, Base58.encode(signature))); - // We'll send empty block summaries message as it's very short - Message blockUnknownMessage = new BlockSummariesMessage(Collections.emptyList()); + // Send generic 'unknown' message as it's very short + Message blockUnknownMessage = peer.getPeersVersion() >= GenericUnknownMessage.MINIMUM_PEER_VERSION + ? new GenericUnknownMessage() + : new BlockSummariesMessage(Collections.emptyList()); blockUnknownMessage.setId(message.getId()); if (!peer.sendMessage(blockUnknownMessage)) peer.disconnect("failed to send block-unknown response"); @@ -1407,11 +1456,15 @@ public class Controller extends Thread { this.stats.getBlockSummariesStats.requests.incrementAndGet(); // If peer's parent signature matches our latest block signature - // then we can short-circuit with an empty response + // then we have no blocks after that and can short-circuit with an empty response BlockData chainTip = getChainTip(); if (chainTip != null && Arrays.equals(parentSignature, chainTip.getSignature())) { - Message blockSummariesMessage = new BlockSummariesMessage(Collections.emptyList()); + Message blockSummariesMessage = peer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION + ? new BlockSummariesV2Message(Collections.emptyList()) + : new BlockSummariesMessage(Collections.emptyList()); + blockSummariesMessage.setId(message.getId()); + if (!peer.sendMessage(blockSummariesMessage)) peer.disconnect("failed to send block summaries"); @@ -1467,7 +1520,9 @@ public class Controller extends Thread { this.stats.getBlockSummariesStats.fullyFromCache.incrementAndGet(); } - Message blockSummariesMessage = new BlockSummariesMessage(blockSummaries); + Message blockSummariesMessage = peer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION + ? new BlockSummariesV2Message(blockSummaries) + : new BlockSummariesMessage(blockSummaries); blockSummariesMessage.setId(message.getId()); if (!peer.sendMessage(blockSummariesMessage)) peer.disconnect("failed to send block summaries"); @@ -1542,18 +1597,59 @@ public class Controller extends Thread { // If peer is inbound and we've not updated their height // then this is probably their initial HEIGHT_V2 message // so they need a corresponding HEIGHT_V2 message from us - if (!peer.isOutbound() && (peer.getChainTipData() == null || peer.getChainTipData().getLastHeight() == null)) - peer.sendMessage(Network.getInstance().buildHeightMessage(peer, getChainTip())); + if (!peer.isOutbound() && peer.getChainTipData() == null) { + Message responseMessage = Network.getInstance().buildHeightOrChainTipInfo(peer); + + if (responseMessage == null || !peer.sendMessage(responseMessage)) { + peer.disconnect("failed to send our chain tip info"); + return; + } + } } // Update peer chain tip data - PeerChainTipData newChainTipData = new PeerChainTipData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getTimestamp(), heightV2Message.getMinterPublicKey()); + BlockSummaryData newChainTipData = new BlockSummaryData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getMinterPublicKey(), heightV2Message.getTimestamp()); peer.setChainTipData(newChainTipData); // Potentially synchronize Synchronizer.getInstance().requestSync(); } + private void onNetworkBlockSummariesV2Message(Peer peer, Message message) { + BlockSummariesV2Message blockSummariesV2Message = (BlockSummariesV2Message) message; + + if (!Settings.getInstance().isLite()) { + // If peer is inbound and we've not updated their height + // then this is probably their initial BLOCK_SUMMARIES_V2 message + // so they need a corresponding BLOCK_SUMMARIES_V2 message from us + if (!peer.isOutbound() && peer.getChainTipData() == null) { + Message responseMessage = Network.getInstance().buildHeightOrChainTipInfo(peer); + + if (responseMessage == null || !peer.sendMessage(responseMessage)) { + peer.disconnect("failed to send our chain tip info"); + return; + } + } + } + + if (message.hasId()) { + /* + * Experimental proof-of-concept: discard messages with ID + * These are 'late' reply messages received after timeout has expired, + * having been passed upwards from Peer to Network to Controller. + * Hence, these are NOT simple "here's my chain tip" broadcasts from other peers. + */ + LOGGER.debug("Discarding late {} message with ID {} from {}", message.getType().name(), message.getId(), peer); + return; + } + + // Update peer chain tip data + peer.setChainTipSummaries(blockSummariesV2Message.getBlockSummaries()); + + // Potentially synchronize + Synchronizer.getInstance().requestSync(); + } + private void onNetworkGetAccountMessage(Peer peer, Message message) { GetAccountMessage getAccountMessage = (GetAccountMessage) message; String address = getAccountMessage.getAddress(); @@ -1569,8 +1665,8 @@ public class Controller extends Thread { // Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT request for unknown account %s", peer, address)); - // We'll send empty block summaries message as it's very short - Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList()); + // Send generic 'unknown' message as it's very short + Message accountUnknownMessage = new GenericUnknownMessage(); accountUnknownMessage.setId(message.getId()); if (!peer.sendMessage(accountUnknownMessage)) peer.disconnect("failed to send account-unknown response"); @@ -1605,8 +1701,8 @@ public class Controller extends Thread { // Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT_BALANCE request for unknown account %s and asset ID %d", peer, address, assetId)); - // We'll send empty block summaries message as it's very short - Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList()); + // Send generic 'unknown' message as it's very short + Message accountUnknownMessage = new GenericUnknownMessage(); accountUnknownMessage.setId(message.getId()); if (!peer.sendMessage(accountUnknownMessage)) peer.disconnect("failed to send account-unknown response"); @@ -1649,8 +1745,8 @@ public class Controller extends Thread { // Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT_TRANSACTIONS request for unknown account %s", peer, address)); - // We'll send empty block summaries message as it's very short - Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList()); + // Send generic 'unknown' message as it's very short + Message accountUnknownMessage = new GenericUnknownMessage(); accountUnknownMessage.setId(message.getId()); if (!peer.sendMessage(accountUnknownMessage)) peer.disconnect("failed to send account-unknown response"); @@ -1686,8 +1782,8 @@ public class Controller extends Thread { // Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT_NAMES request for unknown account %s", peer, address)); - // We'll send empty block summaries message as it's very short - Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList()); + // Send generic 'unknown' message as it's very short + Message accountUnknownMessage = new GenericUnknownMessage(); accountUnknownMessage.setId(message.getId()); if (!peer.sendMessage(accountUnknownMessage)) peer.disconnect("failed to send account-unknown response"); @@ -1721,8 +1817,8 @@ public class Controller extends Thread { // Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout LOGGER.debug(() -> String.format("Sending 'name unknown' response to peer %s for GET_NAME request for unknown name %s", peer, name)); - // We'll send empty block summaries message as it's very short - Message nameUnknownMessage = new BlockSummariesMessage(Collections.emptyList()); + // Send generic 'unknown' message as it's very short + Message nameUnknownMessage = new GenericUnknownMessage(); nameUnknownMessage.setId(message.getId()); if (!peer.sendMessage(nameUnknownMessage)) peer.disconnect("failed to send name-unknown response"); @@ -1770,14 +1866,14 @@ public class Controller extends Thread { continue; } - final PeerChainTipData peerChainTipData = peer.getChainTipData(); + BlockSummaryData peerChainTipData = peer.getChainTipData(); if (peerChainTipData == null) { iterator.remove(); continue; } // Disregard peers that don't have a recent block - if (peerChainTipData.getLastBlockTimestamp() == null || peerChainTipData.getLastBlockTimestamp() < minLatestBlockTimestamp) { + if (peerChainTipData.getTimestamp() == null || peerChainTipData.getTimestamp() < minLatestBlockTimestamp) { iterator.remove(); continue; } @@ -1805,6 +1901,10 @@ public class Controller extends Thread { if (latestBlockData == null || latestBlockData.getTimestamp() < minLatestBlockTimestamp) return false; + if (Settings.getInstance().isSingleNodeTestnet()) + // Single node testnets won't have peers, so we can assume up to date from this point + return true; + // Needs a mutable copy of the unmodifiableList List peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers()); if (peers == null) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 482e9d90..fd2c38df 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -1,12 +1,16 @@ package org.qortal.controller; +import com.google.common.hash.HashCode; import com.google.common.primitives.Longs; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.account.Account; import org.qortal.account.PrivateKeyAccount; -import org.qortal.account.PublicKeyAccount; +import org.qortal.block.Block; import org.qortal.block.BlockChain; +import org.qortal.crypto.Crypto; +import org.qortal.crypto.MemoryPoW; +import org.qortal.crypto.Qortal25519Extras; import org.qortal.data.account.MintingAccountData; import org.qortal.data.account.RewardShareData; import org.qortal.data.network.OnlineAccountData; @@ -16,272 +20,468 @@ import org.qortal.network.message.*; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; import org.qortal.utils.Base58; import org.qortal.utils.NTP; +import org.qortal.utils.NamedThreadFactory; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.*; +import java.util.concurrent.*; import java.util.stream.Collectors; -public class OnlineAccountsManager extends Thread { - - private class OurOnlineAccountsThread extends Thread { - - public void run() { - try { - while (!isStopping) { - Thread.sleep(10000L); - - // Refresh our online accounts signatures? - sendOurOnlineAccountsInfo(); - - } - } catch (InterruptedException e) { - // Fall through to exit thread - } - } - } - +public class OnlineAccountsManager { private static final Logger LOGGER = LogManager.getLogger(OnlineAccountsManager.class); - private static OnlineAccountsManager instance; + // 'Current' as in 'now' + + /** + * How long online accounts signatures last before they expire. + */ + private static final long ONLINE_TIMESTAMP_MODULUS_V1 = 5 * 60 * 1000L; + private static final long ONLINE_TIMESTAMP_MODULUS_V2 = 30 * 60 * 1000L; + + /** + * How many 'current' timestamp-sets of online accounts we cache. + */ + private static final int MAX_CACHED_TIMESTAMP_SETS = 2; + + /** + * How many timestamp-sets of online accounts we cache for 'latest blocks'. + */ + private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 3; + + private static final long ONLINE_ACCOUNTS_QUEUE_INTERVAL = 100L; // ms + private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms + private static final long ONLINE_ACCOUNTS_COMPUTE_INTERVAL = 5 * 1000L; // ms + private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 60 * 1000L; // ms + // After switching to a new online timestamp, we "burst" the online accounts requests + // at an increased interval for a specified amount of time + private static final long ONLINE_ACCOUNTS_BROADCAST_BURST_INTERVAL = 5 * 1000L; // ms + private static final long ONLINE_ACCOUNTS_BROADCAST_BURST_LENGTH = 5 * 60 * 1000L; // ms + + private static final long ONLINE_ACCOUNTS_COMPUTE_INITIAL_SLEEP_INTERVAL = 30 * 1000L; // ms + + // MemoryPoW - mainnet + public static final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes + public static final int POW_DIFFICULTY_V1 = 18; // leading zero bits + public static final int POW_DIFFICULTY_V2 = 19; // leading zero bits + + // MemoryPoW - testnet + public static final int POW_BUFFER_SIZE_TESTNET = 1 * 1024 * 1024; // bytes + public static final int POW_DIFFICULTY_TESTNET = 5; // leading zero bits + + // IMPORTANT: if we ever need to dynamically modify the buffer size using a feature trigger, the + // pre-allocated buffer below will NOT work, and we should instead use a dynamically allocated + // one for the transition period. + private static long[] POW_VERIFY_WORK_BUFFER = new long[getPoWBufferSize() / 8]; + + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4, new NamedThreadFactory("OnlineAccounts")); private volatile boolean isStopping = false; - // To do with online accounts list - private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms - private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 1 * 60 * 1000L; // ms - public static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000L; - private static final long LAST_SEEN_EXPIRY_PERIOD = (ONLINE_TIMESTAMP_MODULUS * 2) + (1 * 60 * 1000L); - /** How many (latest) blocks' worth of online accounts we cache */ - private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 2; - private static final long ONLINE_ACCOUNTS_V2_PEER_VERSION = 0x0300020000L; + private final Set onlineAccountsImportQueue = ConcurrentHashMap.newKeySet(); - private long onlineAccountsTasksTimestamp = Controller.startTime + ONLINE_ACCOUNTS_TASKS_INTERVAL; // ms + /** + * Cache of 'current' online accounts, keyed by timestamp + */ + private final Map> currentOnlineAccounts = new ConcurrentHashMap<>(); + /** + * Cache of hash-summary of 'current' online accounts, keyed by timestamp, then leading byte of public key. + */ + private final Map> currentOnlineAccountsHashes = new ConcurrentHashMap<>(); - private final List onlineAccountsImportQueue = Collections.synchronizedList(new ArrayList<>()); + /** + * Cache of online accounts for latest blocks - not necessarily 'current' / now. + * Probably only accessed / modified by a single Synchronizer thread. + */ + private final SortedMap> latestBlocksOnlineAccounts = new ConcurrentSkipListMap<>(); - private boolean hasOnlineAccounts = false; + private long lastOnlineAccountsRequest = 0; + private boolean hasOurOnlineAccounts = false; - /** Cache of current 'online accounts' */ - List onlineAccounts = new ArrayList<>(); - /** Cache of latest blocks' online accounts */ - Deque> latestBlocksOnlineAccounts = new ArrayDeque<>(MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS); - - public OnlineAccountsManager() { + public static long getOnlineTimestampModulus() { + Long now = NTP.getTime(); + if (now != null && now >= BlockChain.getInstance().getOnlineAccountsModulusV2Timestamp()) { + return ONLINE_TIMESTAMP_MODULUS_V2; + } + return ONLINE_TIMESTAMP_MODULUS_V1; + } + public static Long getCurrentOnlineAccountTimestamp() { + Long now = NTP.getTime(); + if (now == null) + return null; + long onlineTimestampModulus = getOnlineTimestampModulus(); + return (now / onlineTimestampModulus) * onlineTimestampModulus; } - public static synchronized OnlineAccountsManager getInstance() { - if (instance == null) { - instance = new OnlineAccountsManager(); - } - - return instance; + public static long toOnlineAccountTimestamp(long timestamp) { + return (timestamp / getOnlineTimestampModulus()) * getOnlineTimestampModulus(); } - public void run() { + private static int getPoWBufferSize() { + if (Settings.getInstance().isTestNet()) + return POW_BUFFER_SIZE_TESTNET; - // Start separate thread to prepare our online accounts - // This could be converted to a thread pool later if more concurrency is needed - OurOnlineAccountsThread ourOnlineAccountsThread = new OurOnlineAccountsThread(); - ourOnlineAccountsThread.start(); + return POW_BUFFER_SIZE; + } - try { - while (!Controller.isStopping()) { - Thread.sleep(100L); + private static int getPoWDifficulty(long timestamp) { + if (Settings.getInstance().isTestNet()) + return POW_DIFFICULTY_TESTNET; - final Long now = NTP.getTime(); - if (now == null) { - continue; - } + if (timestamp >= BlockChain.getInstance().getIncreaseOnlineAccountsDifficultyTimestamp()) + return POW_DIFFICULTY_V2; - // Perform tasks to do with managing online accounts list - if (now >= onlineAccountsTasksTimestamp) { - onlineAccountsTasksTimestamp = now + ONLINE_ACCOUNTS_TASKS_INTERVAL; - performOnlineAccountsTasks(); - } + return POW_DIFFICULTY_V1; + } - // Process queued online account verifications - this.processOnlineAccountsImportQueue(); + private OnlineAccountsManager() { + } - } - } catch (InterruptedException e) { - // Fall through to exit thread - } + private static class SingletonContainer { + private static final OnlineAccountsManager INSTANCE = new OnlineAccountsManager(); + } - ourOnlineAccountsThread.interrupt(); + public static OnlineAccountsManager getInstance() { + return SingletonContainer.INSTANCE; + } + + public void start() { + // Expire old online accounts signatures + executor.scheduleAtFixedRate(this::expireOldOnlineAccounts, ONLINE_ACCOUNTS_TASKS_INTERVAL, ONLINE_ACCOUNTS_TASKS_INTERVAL, TimeUnit.MILLISECONDS); + + // Request online accounts from peers + executor.scheduleAtFixedRate(this::requestRemoteOnlineAccounts, ONLINE_ACCOUNTS_BROADCAST_BURST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_BURST_INTERVAL, TimeUnit.MILLISECONDS); + + // Process import queue + executor.scheduleWithFixedDelay(this::processOnlineAccountsImportQueue, ONLINE_ACCOUNTS_QUEUE_INTERVAL, ONLINE_ACCOUNTS_QUEUE_INTERVAL, TimeUnit.MILLISECONDS); + + // Send our online accounts (using increased initial delay) + // This allows some time for initial online account lists to be retrieved, and + // reduces the chances of the same nonce being computed twice + executor.scheduleAtFixedRate(this::sendOurOnlineAccountsInfo, ONLINE_ACCOUNTS_COMPUTE_INITIAL_SLEEP_INTERVAL, ONLINE_ACCOUNTS_COMPUTE_INTERVAL, TimeUnit.MILLISECONDS); } public void shutdown() { isStopping = true; - this.interrupt(); - } - - public boolean hasOnlineAccounts() { - return this.hasOnlineAccounts; - } - - - // Online accounts import queue - - private void processOnlineAccountsImportQueue() { - if (this.onlineAccountsImportQueue.isEmpty()) { - // Nothing to do - return; - } - - LOGGER.debug("Processing online accounts import queue (size: {})", this.onlineAccountsImportQueue.size()); - - try (final Repository repository = RepositoryManager.getRepository()) { - - List onlineAccountDataCopy = new ArrayList<>(this.onlineAccountsImportQueue); - for (OnlineAccountData onlineAccountData : onlineAccountDataCopy) { - if (isStopping) { - return; - } - - this.verifyAndAddAccount(repository, onlineAccountData); - - // Remove from queue - onlineAccountsImportQueue.remove(onlineAccountData); - } - - LOGGER.debug("Finished processing online accounts import queue"); - - } catch (DataException e) { - LOGGER.error(String.format("Repository issue while verifying online accounts"), e); - } - } - - - // Utilities - - private void verifyAndAddAccount(Repository repository, OnlineAccountData onlineAccountData) throws DataException { - final Long now = NTP.getTime(); - if (now == null) - return; - - PublicKeyAccount otherAccount = new PublicKeyAccount(repository, onlineAccountData.getPublicKey()); - - // Check timestamp is 'recent' here - if (Math.abs(onlineAccountData.getTimestamp() - now) > ONLINE_TIMESTAMP_MODULUS * 2) { - LOGGER.trace(() -> String.format("Rejecting online account %s with out of range timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp())); - return; - } - - // Verify - byte[] data = Longs.toByteArray(onlineAccountData.getTimestamp()); - if (!otherAccount.verify(onlineAccountData.getSignature(), data)) { - LOGGER.trace(() -> String.format("Rejecting invalid online account %s", otherAccount.getAddress())); - return; - } - - // Qortal: check online account is actually reward-share - RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(onlineAccountData.getPublicKey()); - if (rewardShareData == null) { - // Reward-share doesn't even exist - probably not a good sign - LOGGER.trace(() -> String.format("Rejecting unknown online reward-share public key %s", Base58.encode(onlineAccountData.getPublicKey()))); - return; - } - - Account mintingAccount = new Account(repository, rewardShareData.getMinter()); - if (!mintingAccount.canMint()) { - // Minting-account component of reward-share can no longer mint - disregard - LOGGER.trace(() -> String.format("Rejecting online reward-share with non-minting account %s", mintingAccount.getAddress())); - return; - } - - synchronized (this.onlineAccounts) { - OnlineAccountData existingAccountData = this.onlineAccounts.stream().filter(account -> Arrays.equals(account.getPublicKey(), onlineAccountData.getPublicKey())).findFirst().orElse(null); - - if (existingAccountData != null) { - if (existingAccountData.getTimestamp() < onlineAccountData.getTimestamp()) { - this.onlineAccounts.remove(existingAccountData); - - LOGGER.trace(() -> String.format("Updated online account %s with timestamp %d (was %d)", otherAccount.getAddress(), onlineAccountData.getTimestamp(), existingAccountData.getTimestamp())); - } else { - LOGGER.trace(() -> String.format("Not updating existing online account %s", otherAccount.getAddress())); - - return; - } - } else { - LOGGER.trace(() -> String.format("Added online account %s with timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp())); - } - - this.onlineAccounts.add(onlineAccountData); - } + executor.shutdownNow(); } + // Testing support public void ensureTestingAccountsOnline(PrivateKeyAccount... onlineAccounts) { if (!BlockChain.getInstance().isTestChain()) { LOGGER.warn("Ignoring attempt to ensure test account is online for non-test chain!"); return; } - final Long now = NTP.getTime(); - if (now == null) + final Long onlineAccountsTimestamp = getCurrentOnlineAccountTimestamp(); + if (onlineAccountsTimestamp == null) return; - final long onlineAccountsTimestamp = toOnlineAccountTimestamp(now); byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp); - synchronized (this.onlineAccounts) { - this.onlineAccounts.clear(); + Set replacementAccounts = new HashSet<>(); + for (PrivateKeyAccount onlineAccount : onlineAccounts) { + // Check mintingAccount is actually reward-share? - for (PrivateKeyAccount onlineAccount : onlineAccounts) { - // Check mintingAccount is actually reward-share? + byte[] signature = Qortal25519Extras.signForAggregation(onlineAccount.getPrivateKey(), timestampBytes); + byte[] publicKey = onlineAccount.getPublicKey(); - byte[] signature = onlineAccount.sign(timestampBytes); - byte[] publicKey = onlineAccount.getPublicKey(); + Integer nonce = new Random().nextInt(500000); - OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey); - this.onlineAccounts.add(ourOnlineAccountData); + OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, nonce); + replacementAccounts.add(ourOnlineAccountData); + } + + this.currentOnlineAccounts.clear(); + addAccounts(replacementAccounts); + } + + // Online accounts import queue + + private void processOnlineAccountsImportQueue() { + if (this.onlineAccountsImportQueue.isEmpty()) + // Nothing to do + return; + + LOGGER.debug("Processing online accounts import queue (size: {})", this.onlineAccountsImportQueue.size()); + + Set onlineAccountsToAdd = new HashSet<>(); + Set onlineAccountsToRemove = new HashSet<>(); + try (final Repository repository = RepositoryManager.getRepository()) { + for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) { + if (isStopping) + return; + + // Skip this account if it's already validated + Set onlineAccounts = this.currentOnlineAccounts.get(onlineAccountData.getTimestamp()); + if (onlineAccounts != null && onlineAccounts.contains(onlineAccountData)) { + // We have already validated this online account + onlineAccountsImportQueue.remove(onlineAccountData); + continue; + } + + boolean isValid = this.isValidCurrentAccount(repository, onlineAccountData); + if (isValid) + onlineAccountsToAdd.add(onlineAccountData); + + // Don't remove from the queue yet - we'll do this at the end of the process + // This prevents duplicates being added to the queue whilst it's being processed + onlineAccountsToRemove.add(onlineAccountData); } + } catch (DataException e) { + LOGGER.error("Repository issue while verifying online accounts", e); + + } finally { + if (!onlineAccountsToAdd.isEmpty()) { + LOGGER.debug("Merging {} validated online accounts from import queue", onlineAccountsToAdd.size()); + addAccounts(onlineAccountsToAdd); + } + onlineAccountsImportQueue.removeAll(onlineAccountsToRemove); } } - private void performOnlineAccountsTasks() { + /** + * Check if supplied onlineAccountData is superior (i.e. has a nonce value) than existing record. + * Two entries are considered equal even if the nonce differs, to prevent multiple variations + * co-existing. For this reason, we need to be able to check if a new OnlineAccountData entry should + * replace the existing one, which may be missing the nonce. + * @param onlineAccountData + * @return true if supplied data is superior to existing entry + */ + private boolean isOnlineAccountsDataSuperior(OnlineAccountData onlineAccountData) { + if (onlineAccountData.getNonce() == null || onlineAccountData.getNonce() < 0) { + // New online account data has no usable nonce value, so it won't be better than anything we already have + return false; + } + + // New online account data has a nonce value, so check if there is any existing data to compare against + Set existingOnlineAccountsForTimestamp = this.currentOnlineAccounts.get(onlineAccountData.getTimestamp()); + if (existingOnlineAccountsForTimestamp == null) { + // No existing online accounts data with this timestamp yet + return false; + } + + // Check if a duplicate entry exists + OnlineAccountData existingOnlineAccountData = null; + for (OnlineAccountData existingAccount : existingOnlineAccountsForTimestamp) { + if (existingAccount.equals(onlineAccountData)) { + // Found existing online account data + existingOnlineAccountData = existingAccount; + break; + } + } + + if (existingOnlineAccountData == null) { + // No existing online accounts data, so nothing to compare + return false; + } + + if (existingOnlineAccountData.getNonce() == null || existingOnlineAccountData.getNonce() < 0) { + // Existing data has no usable nonce value(s) so we want to replace it with the new one + return true; + } + + // Both new and old data have nonce values so the new data isn't considered superior + return false; + } + + + // Utilities + + public static byte[] xorByteArrayInPlace(byte[] inplaceArray, byte[] otherArray) { + if (inplaceArray == null) + return Arrays.copyOf(otherArray, otherArray.length); + + // Start from index 1 to enforce static leading byte + for (int i = 1; i < otherArray.length; i++) + inplaceArray[i] ^= otherArray[i]; + + return inplaceArray; + } + + private static boolean isValidCurrentAccount(Repository repository, OnlineAccountData onlineAccountData) throws DataException { + final Long now = NTP.getTime(); + if (now == null) + return false; + + byte[] rewardSharePublicKey = onlineAccountData.getPublicKey(); + long onlineAccountTimestamp = onlineAccountData.getTimestamp(); + + // Check timestamp is 'recent' here + if (Math.abs(onlineAccountTimestamp - now) > getOnlineTimestampModulus() * 2) { + LOGGER.trace(() -> String.format("Rejecting online account %s with out of range timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp)); + return false; + } + + // Check timestamp is a multiple of online timestamp modulus + if (onlineAccountTimestamp % getOnlineTimestampModulus() != 0) { + LOGGER.trace(() -> String.format("Rejecting online account %s with invalid timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp)); + return false; + } + + // Verify signature + byte[] data = Longs.toByteArray(onlineAccountData.getTimestamp()); + boolean isSignatureValid = Qortal25519Extras.verifyAggregated(rewardSharePublicKey, onlineAccountData.getSignature(), data); + if (!isSignatureValid) { + LOGGER.trace(() -> String.format("Rejecting invalid online account %s", Base58.encode(rewardSharePublicKey))); + return false; + } + + // Qortal: check online account is actually reward-share + RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(rewardSharePublicKey); + if (rewardShareData == null) { + // Reward-share doesn't even exist - probably not a good sign + LOGGER.trace(() -> String.format("Rejecting unknown online reward-share public key %s", Base58.encode(rewardSharePublicKey))); + return false; + } + + Account mintingAccount = new Account(repository, rewardShareData.getMinter()); + if (!mintingAccount.canMint()) { + // Minting-account component of reward-share can no longer mint - disregard + LOGGER.trace(() -> String.format("Rejecting online reward-share with non-minting account %s", mintingAccount.getAddress())); + return false; + } + + // Validate mempow + if (!getInstance().verifyMemoryPoW(onlineAccountData, POW_VERIFY_WORK_BUFFER)) { + LOGGER.trace(() -> String.format("Rejecting online reward-share for account %s due to invalid PoW nonce", mintingAccount.getAddress())); + return false; + } + + return true; + } + + /** Adds accounts, maybe rebuilds hashes, returns whether any new accounts were added / hashes rebuilt. */ + private boolean addAccounts(Collection onlineAccountsToAdd) { + // For keeping track of which hashes to rebuild + Map> hashesToRebuild = new HashMap<>(); + + for (OnlineAccountData onlineAccountData : onlineAccountsToAdd) { + boolean isNewEntry = this.addAccount(onlineAccountData); + + if (isNewEntry) + hashesToRebuild.computeIfAbsent(onlineAccountData.getTimestamp(), k -> new HashSet<>()).add(onlineAccountData.getPublicKey()[0]); + } + + if (hashesToRebuild.isEmpty()) + return false; + + for (var entry : hashesToRebuild.entrySet()) { + Long timestamp = entry.getKey(); + + LOGGER.trace(() -> String.format("Rehashing for timestamp %d and leading bytes %s", + timestamp, + entry.getValue().stream().sorted(Byte::compareUnsigned).map(leadingByte -> String.format("%02x", leadingByte)).collect(Collectors.joining(", ")) + ) + ); + + for (Byte leadingByte : entry.getValue()) { + byte[] pubkeyHash = currentOnlineAccounts.get(timestamp).stream() + .map(OnlineAccountData::getPublicKey) + .filter(publicKey -> leadingByte == publicKey[0]) + .reduce(null, OnlineAccountsManager::xorByteArrayInPlace); + + currentOnlineAccountsHashes.computeIfAbsent(timestamp, k -> new ConcurrentHashMap<>()).put(leadingByte, pubkeyHash); + + LOGGER.trace(() -> String.format("Rebuilt hash %s for timestamp %d and leading byte %02x using %d public keys", + HashCode.fromBytes(pubkeyHash), + timestamp, + leadingByte, + currentOnlineAccounts.get(timestamp).stream() + .map(OnlineAccountData::getPublicKey) + .filter(publicKey -> leadingByte == publicKey[0]) + .count() + )); + } + } + + LOGGER.trace(String.format("we have online accounts for timestamps: %s", String.join(", ", this.currentOnlineAccounts.keySet().stream().map(l -> Long.toString(l)).collect(Collectors.joining(", "))))); + + return true; + } + + private boolean addAccount(OnlineAccountData onlineAccountData) { + byte[] rewardSharePublicKey = onlineAccountData.getPublicKey(); + long onlineAccountTimestamp = onlineAccountData.getTimestamp(); + + Set onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountTimestamp, k -> ConcurrentHashMap.newKeySet()); + + boolean isSuperiorEntry = isOnlineAccountsDataSuperior(onlineAccountData); + if (isSuperiorEntry) + // Remove existing inferior entry so it can be re-added below (it's likely the existing copy is missing a nonce value) + onlineAccounts.remove(onlineAccountData); + + boolean isNewEntry = onlineAccounts.add(onlineAccountData); + + if (isNewEntry) + LOGGER.trace(() -> String.format("Added online account %s with timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp)); + else + LOGGER.trace(() -> String.format("Not updating existing online account %s with timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp)); + + return isNewEntry; + } + + /** + * Expire old entries. + */ + private void expireOldOnlineAccounts() { final Long now = NTP.getTime(); if (now == null) return; - // Expire old entries - final long cutoffThreshold = now - LAST_SEEN_EXPIRY_PERIOD; - synchronized (this.onlineAccounts) { - Iterator iterator = this.onlineAccounts.iterator(); - while (iterator.hasNext()) { - OnlineAccountData onlineAccountData = iterator.next(); - - if (onlineAccountData.getTimestamp() < cutoffThreshold) { - iterator.remove(); - - LOGGER.trace(() -> { - PublicKeyAccount otherAccount = new PublicKeyAccount(null, onlineAccountData.getPublicKey()); - return String.format("Removed expired online account %s with timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp()); - }); - } - } - } - - // Request data from other peers? - if ((this.onlineAccountsTasksTimestamp % ONLINE_ACCOUNTS_BROADCAST_INTERVAL) < ONLINE_ACCOUNTS_TASKS_INTERVAL) { - List safeOnlineAccounts; - synchronized (this.onlineAccounts) { - safeOnlineAccounts = new ArrayList<>(this.onlineAccounts); - } - - Message messageV1 = new GetOnlineAccountsMessage(safeOnlineAccounts); - Message messageV2 = new GetOnlineAccountsV2Message(safeOnlineAccounts); - - Network.getInstance().broadcast(peer -> - peer.getPeersVersion() >= ONLINE_ACCOUNTS_V2_PEER_VERSION ? messageV2 : messageV1 - ); - } + final long cutoffThreshold = now - MAX_CACHED_TIMESTAMP_SETS * getOnlineTimestampModulus(); + this.currentOnlineAccounts.keySet().removeIf(timestamp -> timestamp < cutoffThreshold); + this.currentOnlineAccountsHashes.keySet().removeIf(timestamp -> timestamp < cutoffThreshold); } - private void sendOurOnlineAccountsInfo() { + /** + * Request data from other peers + */ + private void requestRemoteOnlineAccounts() { final Long now = NTP.getTime(); + if (now == null) + return; + + // Don't bother if we're not up to date + if (!Controller.getInstance().isUpToDate()) + return; + + long onlineAccountsTimestamp = getCurrentOnlineAccountTimestamp(); + if (now - onlineAccountsTimestamp >= ONLINE_ACCOUNTS_BROADCAST_BURST_LENGTH) { + // New online timestamp started more than 5 mins ago - we probably don't need to request so frequently + + if (Controller.uptime() < ONLINE_ACCOUNTS_BROADCAST_BURST_LENGTH) { + // The node recently started up, so we should request at the burst interval + // This could allow accounts to move around the network more easily when an auto update is occurring + } + else if (now - lastOnlineAccountsRequest < ONLINE_ACCOUNTS_BROADCAST_INTERVAL) { + // We already requested online accounts in the last minute, so no need to request again + return; + } + } + + LOGGER.debug("Requesting online accounts via broadcast..."); + + lastOnlineAccountsRequest = now; + Message messageV3 = new GetOnlineAccountsV3Message(currentOnlineAccountsHashes); + Network.getInstance().broadcast(peer -> messageV3); + } + + /** + * Send online accounts that are minting on this node. + */ + private void sendOurOnlineAccountsInfo() { + // 'current' timestamp + final Long onlineAccountsTimestamp = getCurrentOnlineAccountTimestamp(); + if (onlineAccountsTimestamp == null) + return; + + Long now = NTP.getTime(); if (now == null) { return; } @@ -292,17 +492,29 @@ public class OnlineAccountsManager extends Thread { return; } + // 'next' timestamp (prioritize this as it's the most important, if mempow active) + final long nextOnlineAccountsTimestamp = toOnlineAccountTimestamp(now) + getOnlineTimestampModulus(); + boolean success = computeOurAccountsForTimestamp(nextOnlineAccountsTimestamp); + if (!success) { + // We didn't compute the required nonce value(s), and so can't proceed until they have been retried + return; + } + + // 'current' timestamp + computeOurAccountsForTimestamp(onlineAccountsTimestamp); + } + + private boolean computeOurAccountsForTimestamp(long onlineAccountsTimestamp) { List mintingAccounts; try (final Repository repository = RepositoryManager.getRepository()) { mintingAccounts = repository.getAccountRepository().getMintingAccounts(); - // We have no accounts, but don't reset timestamp + // We have no accounts to send if (mintingAccounts.isEmpty()) - return; + return false; - // Only reward-share accounts allowed + // Only active reward-shares allowed Iterator iterator = mintingAccounts.iterator(); - int i = 0; while (iterator.hasNext()) { MintingAccountData mintingAccountData = iterator.next(); @@ -319,224 +531,328 @@ public class OnlineAccountsManager extends Thread { iterator.remove(); continue; } - - if (++i > 1+1) { - iterator.remove(); - continue; - } } } catch (DataException e) { LOGGER.warn(String.format("Repository issue trying to fetch minting accounts: %s", e.getMessage())); - return; + return false; } - // 'current' timestamp - final long onlineAccountsTimestamp = toOnlineAccountTimestamp(now); - boolean hasInfoChanged = false; - boolean existingAccountsExist = false; - byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp); List ourOnlineAccounts = new ArrayList<>(); - MINTING_ACCOUNTS: + int remaining = mintingAccounts.size(); for (MintingAccountData mintingAccountData : mintingAccounts) { - PrivateKeyAccount mintingAccount = new PrivateKeyAccount(null, mintingAccountData.getPrivateKey()); + remaining--; + byte[] privateKey = mintingAccountData.getPrivateKey(); + byte[] publicKey = Crypto.toPublicKey(privateKey); - byte[] signature = mintingAccount.sign(timestampBytes); - byte[] publicKey = mintingAccount.getPublicKey(); + // We don't want to compute the online account nonce and signature again if it already exists + Set onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountsTimestamp, k -> ConcurrentHashMap.newKeySet()); + boolean alreadyExists = onlineAccounts.stream().anyMatch(a -> Arrays.equals(a.getPublicKey(), publicKey)); + if (alreadyExists) { + this.hasOurOnlineAccounts = true; - // Our account is online - OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey); - synchronized (this.onlineAccounts) { - Iterator iterator = this.onlineAccounts.iterator(); - while (iterator.hasNext()) { - OnlineAccountData existingOnlineAccountData = iterator.next(); - - if (Arrays.equals(existingOnlineAccountData.getPublicKey(), ourOnlineAccountData.getPublicKey())) { - // If our online account is already present, with same timestamp, then move on to next mintingAccount - if (existingOnlineAccountData.getTimestamp() == onlineAccountsTimestamp) { - existingAccountsExist = true; - continue MINTING_ACCOUNTS; - } - - // If our online account is already present, but with older timestamp, then remove it - iterator.remove(); - break; - } + if (remaining > 0) { + // Move on to next account + continue; + } + else { + // Everything exists, so return true + return true; } - - this.onlineAccounts.add(ourOnlineAccountData); } - LOGGER.trace(() -> String.format("Added our online account %s with timestamp %d", mintingAccount.getAddress(), onlineAccountsTimestamp)); - ourOnlineAccounts.add(ourOnlineAccountData); - hasInfoChanged = true; + // Generate bytes for mempow + byte[] mempowBytes; + try { + mempowBytes = this.getMemoryPoWBytes(publicKey, onlineAccountsTimestamp); + } + catch (IOException e) { + LOGGER.info("Unable to create bytes for MemoryPoW. Moving on to next account..."); + continue; + } + + // Compute nonce + Integer nonce; + try { + nonce = this.computeMemoryPoW(mempowBytes, publicKey, onlineAccountsTimestamp); + if (nonce == null) { + // A nonce is required + return false; + } + } catch (TimeoutException e) { + LOGGER.info(String.format("Timed out computing nonce for account %.8s", Base58.encode(publicKey))); + return false; + } + + byte[] signature = Qortal25519Extras.signForAggregation(privateKey, timestampBytes); + + // Our account is online + OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, nonce); + + // Make sure to verify before adding + if (verifyMemoryPoW(ourOnlineAccountData, null)) { + ourOnlineAccounts.add(ourOnlineAccountData); + } } - // Keep track of whether we have online accounts circulating in the network, for systray status - this.hasOnlineAccounts = existingAccountsExist || !ourOnlineAccounts.isEmpty(); + this.hasOurOnlineAccounts = !ourOnlineAccounts.isEmpty(); + + boolean hasInfoChanged = addAccounts(ourOnlineAccounts); if (!hasInfoChanged) + return false; + + Network.getInstance().broadcast(peer -> new OnlineAccountsV3Message(ourOnlineAccounts)); + + LOGGER.debug("Broadcasted {} online account{} with timestamp {}", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp); + + return true; + } + + + + // MemoryPoW + + private byte[] getMemoryPoWBytes(byte[] publicKey, long onlineAccountsTimestamp) throws IOException { + byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(publicKey); + outputStream.write(timestampBytes); + + return outputStream.toByteArray(); + } + + private Integer computeMemoryPoW(byte[] bytes, byte[] publicKey, long onlineAccountsTimestamp) throws TimeoutException { + LOGGER.info(String.format("Computing nonce for account %.8s and timestamp %d...", Base58.encode(publicKey), onlineAccountsTimestamp)); + + // Calculate the time until the next online timestamp and use it as a timeout when computing the nonce + Long startTime = NTP.getTime(); + final long nextOnlineAccountsTimestamp = toOnlineAccountTimestamp(startTime) + getOnlineTimestampModulus(); + long timeUntilNextTimestamp = nextOnlineAccountsTimestamp - startTime; + + int difficulty = getPoWDifficulty(onlineAccountsTimestamp); + Integer nonce = MemoryPoW.compute2(bytes, getPoWBufferSize(), difficulty, timeUntilNextTimestamp); + + double totalSeconds = (NTP.getTime() - startTime) / 1000.0f; + int minutes = (int) ((totalSeconds % 3600) / 60); + int seconds = (int) (totalSeconds % 60); + double hashRate = nonce / totalSeconds; + + LOGGER.info(String.format("Computed nonce for timestamp %d and account %.8s: %d. Buffer size: %d. Difficulty: %d. " + + "Time taken: %02d:%02d. Hashrate: %f", onlineAccountsTimestamp, Base58.encode(publicKey), + nonce, getPoWBufferSize(), difficulty, minutes, seconds, hashRate)); + + return nonce; + } + + public boolean verifyMemoryPoW(OnlineAccountData onlineAccountData, long[] workBuffer) { + // Require a valid nonce value + if (onlineAccountData.getNonce() == null || onlineAccountData.getNonce() < 0) { + return false; + } + + int nonce = onlineAccountData.getNonce(); + + byte[] mempowBytes; + try { + mempowBytes = this.getMemoryPoWBytes(onlineAccountData.getPublicKey(), onlineAccountData.getTimestamp()); + } catch (IOException e) { + return false; + } + + // Verify the nonce + return MemoryPoW.verify2(mempowBytes, workBuffer, getPoWBufferSize(), getPoWDifficulty(onlineAccountData.getTimestamp()), nonce); + } + + + /** + * Returns whether online accounts manager has any online accounts with timestamp recent enough to be considered currently online. + */ + // BlockMinter: only calls this to check whether returned list is empty or not, to determine whether minting is even possible or not + public boolean hasOnlineAccounts() { + // 'current' timestamp + final Long onlineAccountsTimestamp = getCurrentOnlineAccountTimestamp(); + if (onlineAccountsTimestamp == null) + return false; + + return this.currentOnlineAccounts.containsKey(onlineAccountsTimestamp); + } + + /** + * Whether we have submitted - or attempted to submit - our online account + * signature(s) to the network. + * @return true if our signature(s) have been submitted recently. + */ + public boolean hasActiveOnlineAccountSignatures() { + final Long minLatestBlockTimestamp = NTP.getTime() - (2 * 60 * 60 * 1000L); + boolean isUpToDate = Controller.getInstance().isUpToDate(minLatestBlockTimestamp); + + return isUpToDate && hasOurOnlineAccounts(); + } + + public boolean hasOurOnlineAccounts() { + return this.hasOurOnlineAccounts; + } + + /** + * Returns list of online accounts matching given timestamp. + */ + // Block::mint() - only wants online accounts with (online) timestamp that matches block's (online) timestamp so they can be added to new block + public List getOnlineAccounts(long onlineTimestamp) { + LOGGER.debug(String.format("caller's timestamp: %d, our timestamps: %s", onlineTimestamp, String.join(", ", this.currentOnlineAccounts.keySet().stream().map(l -> Long.toString(l)).collect(Collectors.joining(", "))))); + + return new ArrayList<>(Set.copyOf(this.currentOnlineAccounts.getOrDefault(onlineTimestamp, Collections.emptySet()))); + } + + /** + * Returns list of online accounts with timestamp recent enough to be considered currently online. + */ + // API: calls this to return list of online accounts - probably expects ALL timestamps - but going to get 'current' from now on + public List getOnlineAccounts() { + // 'current' timestamp + final Long onlineAccountsTimestamp = getCurrentOnlineAccountTimestamp(); + if (onlineAccountsTimestamp == null) + return Collections.emptyList(); + + return getOnlineAccounts(onlineAccountsTimestamp); + } + + // Block processing + + /** + * Removes previously validated entries from block's online accounts. + *

+ * Checks both 'current' and block caches. + *

+ * Typically called by {@link Block#areOnlineAccountsValid()} + */ + public void removeKnown(Set blocksOnlineAccounts, Long timestamp) { + Set onlineAccounts = this.currentOnlineAccounts.get(timestamp); + + // If not 'current' timestamp - try block cache instead + if (onlineAccounts == null) + onlineAccounts = this.latestBlocksOnlineAccounts.get(timestamp); + + if (onlineAccounts != null) + blocksOnlineAccounts.removeAll(onlineAccounts); + } + + /** + * Adds block's online accounts to one of OnlineAccountManager's caches. + *

+ * It is assumed that the online accounts have been verified. + *

+ * Typically called by {@link Block#areOnlineAccountsValid()} + */ + public void addBlocksOnlineAccounts(Set blocksOnlineAccounts, Long timestamp) { + // If these are current accounts, then there is no need to cache them, and should instead rely + // on the more complete entries we already have in self.currentOnlineAccounts. + // Note: since sig-agg, we no longer have individual signatures included in blocks, so we + // mustn't add anything to currentOnlineAccounts from here. + if (this.currentOnlineAccounts.containsKey(timestamp)) return; - Message messageV1 = new OnlineAccountsMessage(ourOnlineAccounts); - Message messageV2 = new OnlineAccountsV2Message(ourOnlineAccounts); + // Add to block cache instead + this.latestBlocksOnlineAccounts.computeIfAbsent(timestamp, k -> ConcurrentHashMap.newKeySet()) + .addAll(blocksOnlineAccounts); - Network.getInstance().broadcast(peer -> - peer.getPeersVersion() >= ONLINE_ACCOUNTS_V2_PEER_VERSION ? messageV2 : messageV1 - ); - - LOGGER.trace(() -> String.format("Broadcasted %d online account%s with timestamp %d", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp)); - } - - public static long toOnlineAccountTimestamp(long timestamp) { - return (timestamp / ONLINE_TIMESTAMP_MODULUS) * ONLINE_TIMESTAMP_MODULUS; - } - - /** Returns list of online accounts with timestamp recent enough to be considered currently online. */ - public List getOnlineAccounts() { - final long onlineTimestamp = toOnlineAccountTimestamp(NTP.getTime()); - - synchronized (this.onlineAccounts) { - return this.onlineAccounts.stream().filter(account -> account.getTimestamp() == onlineTimestamp).collect(Collectors.toList()); - } - } - - - /** Returns cached, unmodifiable list of latest block's online accounts. */ - public List getLatestBlocksOnlineAccounts() { - synchronized (this.latestBlocksOnlineAccounts) { - return this.latestBlocksOnlineAccounts.peekFirst(); - } - } - - /** Caches list of latest block's online accounts. Typically called by Block.process() */ - public void pushLatestBlocksOnlineAccounts(List latestBlocksOnlineAccounts) { - synchronized (this.latestBlocksOnlineAccounts) { - if (this.latestBlocksOnlineAccounts.size() == MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS) - this.latestBlocksOnlineAccounts.pollLast(); - - this.latestBlocksOnlineAccounts.addFirst(latestBlocksOnlineAccounts == null - ? Collections.emptyList() - : Collections.unmodifiableList(latestBlocksOnlineAccounts)); - } - } - - /** Reverts list of latest block's online accounts. Typically called by Block.orphan() */ - public void popLatestBlocksOnlineAccounts() { - synchronized (this.latestBlocksOnlineAccounts) { - this.latestBlocksOnlineAccounts.pollFirst(); + // If block cache has grown too large then we need to trim. + if (this.latestBlocksOnlineAccounts.size() > MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS) { + // However, be careful to trim the opposite end to the entry we just added! + Long firstKey = this.latestBlocksOnlineAccounts.firstKey(); + if (!firstKey.equals(timestamp)) + this.latestBlocksOnlineAccounts.remove(firstKey); + else + this.latestBlocksOnlineAccounts.remove(this.latestBlocksOnlineAccounts.lastKey()); } } // Network handlers - public void onNetworkGetOnlineAccountsMessage(Peer peer, Message message) { - GetOnlineAccountsMessage getOnlineAccountsMessage = (GetOnlineAccountsMessage) message; + public void onNetworkGetOnlineAccountsV3Message(Peer peer, Message message) { + GetOnlineAccountsV3Message getOnlineAccountsMessage = (GetOnlineAccountsV3Message) message; - List excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts(); + Map> peersHashes = getOnlineAccountsMessage.getHashesByTimestampThenByte(); + List outgoingOnlineAccounts = new ArrayList<>(); - // Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts - List accountsToSend; - synchronized (this.onlineAccounts) { - accountsToSend = new ArrayList<>(this.onlineAccounts); - } + // Warning: no double-checking/fetching - we must be ConcurrentMap compatible! + // So no contains()-then-get() or multiple get()s on the same key/map. + // We also use getOrDefault() with emptySet() on currentOnlineAccounts in case corresponding timestamp entry isn't there. + for (var ourOuterMapEntry : currentOnlineAccountsHashes.entrySet()) { + Long timestamp = ourOuterMapEntry.getKey(); - Iterator iterator = accountsToSend.iterator(); + var ourInnerMap = ourOuterMapEntry.getValue(); + var peersInnerMap = peersHashes.get(timestamp); - SEND_ITERATOR: - while (iterator.hasNext()) { - OnlineAccountData onlineAccountData = iterator.next(); + if (peersInnerMap == null) { + // Peer doesn't have this timestamp, so if it's valid (i.e. not too old) then we'd have to send all of ours + Set timestampsOnlineAccounts = this.currentOnlineAccounts.getOrDefault(timestamp, Collections.emptySet()); + outgoingOnlineAccounts.addAll(timestampsOnlineAccounts); - for (int i = 0; i < excludeAccounts.size(); ++i) { - OnlineAccountData excludeAccountData = excludeAccounts.get(i); + LOGGER.trace(() -> String.format("Going to send all %d online accounts for timestamp %d", timestampsOnlineAccounts.size(), timestamp)); + } else { + // Quick cache of which leading bytes to send so we only have to filter once + Set outgoingLeadingBytes = new HashSet<>(); - if (onlineAccountData.getTimestamp() == excludeAccountData.getTimestamp() && Arrays.equals(onlineAccountData.getPublicKey(), excludeAccountData.getPublicKey())) { - iterator.remove(); - continue SEND_ITERATOR; + // We have entries for this timestamp so compare against peer's entries + for (var ourInnerMapEntry : ourInnerMap.entrySet()) { + Byte leadingByte = ourInnerMapEntry.getKey(); + byte[] peersHash = peersInnerMap.get(leadingByte); + + if (!Arrays.equals(ourInnerMapEntry.getValue(), peersHash)) { + // For this leading byte: hashes don't match or peer doesn't have entry + // Send all online accounts for this timestamp and leading byte + outgoingLeadingBytes.add(leadingByte); + } } + + int beforeAddSize = outgoingOnlineAccounts.size(); + + this.currentOnlineAccounts.getOrDefault(timestamp, Collections.emptySet()).stream() + .filter(account -> outgoingLeadingBytes.contains(account.getPublicKey()[0])) + .forEach(outgoingOnlineAccounts::add); + + if (outgoingOnlineAccounts.size() > beforeAddSize) + LOGGER.trace(String.format("Going to send %d online accounts for timestamp %d and leading bytes %s", + outgoingOnlineAccounts.size() - beforeAddSize, + timestamp, + outgoingLeadingBytes.stream().sorted(Byte::compareUnsigned).map(leadingByte -> String.format("%02x", leadingByte)).collect(Collectors.joining(", ")) + ) + ); } } - Message onlineAccountsMessage = new OnlineAccountsMessage(accountsToSend); - peer.sendMessage(onlineAccountsMessage); + peer.sendMessage(new OnlineAccountsV3Message(outgoingOnlineAccounts)); - LOGGER.trace(() -> String.format("Sent %d of our %d online accounts to %s", accountsToSend.size(), this.onlineAccounts.size(), peer)); + LOGGER.trace("Sent {} online accounts to {}", outgoingOnlineAccounts.size(), peer); } - public void onNetworkOnlineAccountsMessage(Peer peer, Message message) { - OnlineAccountsMessage onlineAccountsMessage = (OnlineAccountsMessage) message; + public void onNetworkOnlineAccountsV3Message(Peer peer, Message message) { + OnlineAccountsV3Message onlineAccountsMessage = (OnlineAccountsV3Message) message; List peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts(); - LOGGER.trace(() -> String.format("Received %d online accounts from %s", peersOnlineAccounts.size(), peer)); - - try (final Repository repository = RepositoryManager.getRepository()) { - for (OnlineAccountData onlineAccountData : peersOnlineAccounts) - this.verifyAndAddAccount(repository, onlineAccountData); - } catch (DataException e) { - LOGGER.error(String.format("Repository issue while verifying online accounts from peer %s", peer), e); - } - } - - public void onNetworkGetOnlineAccountsV2Message(Peer peer, Message message) { - GetOnlineAccountsV2Message getOnlineAccountsMessage = (GetOnlineAccountsV2Message) message; - - List excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts(); - - // Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts - List accountsToSend; - synchronized (this.onlineAccounts) { - accountsToSend = new ArrayList<>(this.onlineAccounts); - } - - Iterator iterator = accountsToSend.iterator(); - - SEND_ITERATOR: - while (iterator.hasNext()) { - OnlineAccountData onlineAccountData = iterator.next(); - - for (int i = 0; i < excludeAccounts.size(); ++i) { - OnlineAccountData excludeAccountData = excludeAccounts.get(i); - - if (onlineAccountData.getTimestamp() == excludeAccountData.getTimestamp() && Arrays.equals(onlineAccountData.getPublicKey(), excludeAccountData.getPublicKey())) { - iterator.remove(); - continue SEND_ITERATOR; - } - } - } - - Message onlineAccountsMessage = new OnlineAccountsV2Message(accountsToSend); - peer.sendMessage(onlineAccountsMessage); - - LOGGER.trace(() -> String.format("Sent %d of our %d online accounts to %s", accountsToSend.size(), this.onlineAccounts.size(), peer)); - } - - public void onNetworkOnlineAccountsV2Message(Peer peer, Message message) { - OnlineAccountsV2Message onlineAccountsMessage = (OnlineAccountsV2Message) message; - - List peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts(); - LOGGER.debug(String.format("Received %d online accounts from %s", peersOnlineAccounts.size(), peer)); + LOGGER.trace("Received {} online accounts from {}", peersOnlineAccounts.size(), peer); int importCount = 0; // Add any online accounts to the queue that aren't already present for (OnlineAccountData onlineAccountData : peersOnlineAccounts) { - // Do we already know about this online account data? - if (onlineAccounts.contains(onlineAccountData)) { + Set onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountData.getTimestamp(), k -> ConcurrentHashMap.newKeySet()); + if (onlineAccounts.contains(onlineAccountData)) + // We have already validated this online account continue; - } - // Is it already in the import queue? - if (onlineAccountsImportQueue.contains(onlineAccountData)) { - continue; - } + boolean isNewEntry = onlineAccountsImportQueue.add(onlineAccountData); - onlineAccountsImportQueue.add(onlineAccountData); - importCount++; + if (isNewEntry) + importCount++; } - LOGGER.debug(String.format("Added %d online accounts to queue", importCount)); + if (importCount > 0) + LOGGER.debug("Added {} online accounts to queue", importCount); } } diff --git a/src/main/java/org/qortal/controller/PirateChainWalletController.java b/src/main/java/org/qortal/controller/PirateChainWalletController.java new file mode 100644 index 00000000..333c2cda --- /dev/null +++ b/src/main/java/org/qortal/controller/PirateChainWalletController.java @@ -0,0 +1,404 @@ +package org.qortal.controller; + +import com.rust.litewalletjni.LiteWalletJni; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONException; +import org.json.JSONObject; +import org.qortal.arbitrary.ArbitraryDataFile; +import org.qortal.arbitrary.ArbitraryDataReader; +import org.qortal.arbitrary.ArbitraryDataResource; +import org.qortal.arbitrary.exception.MissingDataException; +import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.PirateWallet; +import org.qortal.data.arbitrary.ArbitraryResourceStatus; +import org.qortal.data.transaction.ArbitraryTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.network.Network; +import org.qortal.network.Peer; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; +import org.qortal.transaction.ArbitraryTransaction; +import org.qortal.utils.ArbitraryTransactionUtils; +import org.qortal.utils.Base58; +import org.qortal.utils.FilesystemUtils; +import org.qortal.utils.NTP; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; + +public class PirateChainWalletController extends Thread { + + protected static final Logger LOGGER = LogManager.getLogger(PirateChainWalletController.class); + + private static PirateChainWalletController instance; + + final private static long SAVE_INTERVAL = 60 * 60 * 1000L; // 1 hour + private long lastSaveTime = 0L; + + private boolean running; + private PirateWallet currentWallet = null; + private boolean shouldLoadWallet = false; + private String loadStatus = null; + + private static String qdnWalletSignature = "EsfUw54perxkEtfoUoL7Z97XPrNsZRZXePVZPz3cwRm9qyEPSofD5KmgVpDqVitQp7LhnZRmL6z2V9hEe1YS45T"; + + + private PirateChainWalletController() { + this.running = true; + } + + public static PirateChainWalletController getInstance() { + if (instance == null) + instance = new PirateChainWalletController(); + + return instance; + } + + @Override + public void run() { + Thread.currentThread().setName("Pirate Chain Wallet Controller"); + + try { + while (running && !Controller.isStopping()) { + Thread.sleep(1000); + + // Wait until we have a request to load the wallet + if (!shouldLoadWallet) { + continue; + } + + if (!LiteWalletJni.isLoaded()) { + this.loadLibrary(); + + // If still not loaded, sleep to prevent too many requests + if (!LiteWalletJni.isLoaded()) { + Thread.sleep(5 * 1000); + continue; + } + } + + // Wallet is downloaded, so clear the status + this.loadStatus = null; + + if (this.currentWallet == null) { + // Nothing to do yet + continue; + } + if (this.currentWallet.isNullSeedWallet()) { + // Don't sync the null seed wallet + continue; + } + + LOGGER.debug("Syncing Pirate Chain wallet..."); + String response = LiteWalletJni.execute("sync", ""); + LOGGER.debug("sync response: {}", response); + + try { + JSONObject json = new JSONObject(response); + if (json.has("result")) { + String result = json.getString("result"); + + // We may have to set wallet to ready if this is the first ever successful sync + if (Objects.equals(result, "success")) { + this.currentWallet.setReady(true); + } + } + } catch (JSONException e) { + LOGGER.info("Unable to interpret JSON", e); + } + + // Rate limit sync attempts + Thread.sleep(30000); + + // Save wallet if needed + Long now = NTP.getTime(); + if (now != null && now-SAVE_INTERVAL >= this.lastSaveTime) { + this.saveCurrentWallet(); + } + } + } catch (InterruptedException e) { + // Fall-through to exit + } + } + + public void shutdown() { + // Save the wallet + this.saveCurrentWallet(); + + this.running = false; + this.interrupt(); + } + + + // QDN & wallet libraries + + private void loadLibrary() throws InterruptedException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Check if architecture is supported + String libFileName = PirateChainWalletController.getRustLibFilename(); + if (libFileName == null) { + String osName = System.getProperty("os.name"); + String osArchitecture = System.getProperty("os.arch"); + this.loadStatus = String.format("Unsupported architecture (%s %s)", osName, osArchitecture); + return; + } + + // Check if the library exists in the wallets folder + Path libDirectory = PirateChainWalletController.getRustLibOuterDirectory(); + Path libPath = Paths.get(libDirectory.toString(), libFileName); + if (Files.exists(libPath)) { + // Already downloaded; we can load the library right away + LiteWalletJni.loadLibrary(); + return; + } + + // Library not found, so check if we've fetched the resource from QDN + ArbitraryTransactionData t = this.getTransactionData(repository); + if (t == null) { + // Can't find the transaction - maybe on a different chain? + return; + } + + // Wait until we have a sufficient number of peers to attempt QDN downloads + List handshakedPeers = Network.getInstance().getImmutableHandshakedPeers(); + if (handshakedPeers.size() < Settings.getInstance().getMinBlockchainPeers()) { + // Wait for more peers + this.loadStatus = String.format("Searching for peers..."); + return; + } + + // Build resource + ArbitraryDataReader arbitraryDataReader = new ArbitraryDataReader(t.getName(), + ArbitraryDataFile.ResourceIdType.NAME, t.getService(), t.getIdentifier()); + try { + arbitraryDataReader.loadSynchronously(false); + } catch (MissingDataException e) { + LOGGER.info("Missing data when loading Pirate Chain library"); + } + + // Check its status + ArbitraryResourceStatus status = ArbitraryTransactionUtils.getStatus( + t.getService(), t.getName(), t.getIdentifier(), false); + + if (status.getStatus() != ArbitraryResourceStatus.Status.READY) { + LOGGER.info("Not ready yet: {}", status.getTitle()); + this.loadStatus = String.format("Downloading files from QDN... (%d / %d)", status.getLocalChunkCount(), status.getTotalChunkCount()); + return; + } + + // Files are downloaded, so copy the necessary files to the wallets folder + // Delete the wallets/*/lib directory first, in case earlier versions of the wallet are present + Path walletsLibDirectory = PirateChainWalletController.getWalletsLibDirectory(); + if (Files.exists(walletsLibDirectory)) { + FilesystemUtils.safeDeleteDirectory(walletsLibDirectory, false); + } + Files.createDirectories(libDirectory); + FileUtils.copyDirectory(arbitraryDataReader.getFilePath().toFile(), libDirectory.toFile()); + + // Clear reader cache so only one copy exists + ArbitraryDataResource resource = new ArbitraryDataResource(t.getName(), + ArbitraryDataFile.ResourceIdType.NAME, t.getService(), t.getIdentifier()); + resource.deleteCache(); + + // Finally, load the library + LiteWalletJni.loadLibrary(); + + } catch (DataException e) { + LOGGER.error("Repository issue when loading Pirate Chain library", e); + } catch (IOException e) { + LOGGER.error("Error when loading Pirate Chain library", e); + } + } + + private ArbitraryTransactionData getTransactionData(Repository repository) { + try { + byte[] signature = Base58.decode(qdnWalletSignature); + TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); + if (!(transactionData instanceof ArbitraryTransactionData)) + return null; + + ArbitraryTransaction arbitraryTransaction = new ArbitraryTransaction(repository, transactionData); + if (arbitraryTransaction != null) { + return (ArbitraryTransactionData) arbitraryTransaction.getTransactionData(); + } + + return null; + } catch (DataException e) { + return null; + } + } + + public static String getRustLibFilename() { + String osName = System.getProperty("os.name"); + String osArchitecture = System.getProperty("os.arch"); + + if (osName.equals("Mac OS X") && osArchitecture.equals("x86_64")) { + return "librust-macos-x86_64.dylib"; + } + else if ((osName.equals("Linux") || osName.equals("FreeBSD")) && osArchitecture.equals("aarch64")) { + return "librust-linux-aarch64.so"; + } + else if ((osName.equals("Linux") || osName.equals("FreeBSD")) && osArchitecture.equals("amd64")) { + return "librust-linux-x86_64.so"; + } + else if (osName.contains("Windows") && osArchitecture.equals("amd64")) { + return "librust-windows-x86_64.dll"; + } + + return null; + } + + public static Path getWalletsLibDirectory() { + return Paths.get(Settings.getInstance().getWalletsPath(), "PirateChain", "lib"); + } + + public static Path getRustLibOuterDirectory() { + String sigPrefix = qdnWalletSignature.substring(0, 8); + return Paths.get(Settings.getInstance().getWalletsPath(), "PirateChain", "lib", sigPrefix); + } + + + // Wallet functions + + public boolean initWithEntropy58(String entropy58) { + return this.initWithEntropy58(entropy58, false); + } + + public boolean initNullSeedWallet() { + return this.initWithEntropy58(Base58.encode(new byte[32]), true); + } + + private boolean initWithEntropy58(String entropy58, boolean isNullSeedWallet) { + // If the JNI library isn't loaded yet then we can't proceed + if (!LiteWalletJni.isLoaded()) { + shouldLoadWallet = true; + return false; + } + + byte[] entropyBytes = Base58.decode(entropy58); + + if (entropyBytes == null || entropyBytes.length != 32) { + LOGGER.info("Invalid entropy bytes"); + return false; + } + + if (this.currentWallet != null) { + if (this.currentWallet.entropyBytesEqual(entropyBytes)) { + // Wallet already active - nothing to do + return true; + } + else { + // Different wallet requested - close the existing one and switch over + this.closeCurrentWallet(); + } + } + + try { + this.currentWallet = new PirateWallet(entropyBytes, isNullSeedWallet); + if (!this.currentWallet.isReady()) { + // Don't persist wallets that aren't ready + this.currentWallet = null; + } + return true; + } catch (IOException e) { + LOGGER.info("Unable to initialize wallet: {}", e.getMessage()); + } + + return false; + } + + private void saveCurrentWallet() { + if (this.currentWallet == null) { + // Nothing to do + return; + } + try { + if (this.currentWallet.save()) { + Long now = NTP.getTime(); + if (now != null) { + this.lastSaveTime = now; + } + } + } catch (IOException e) { + LOGGER.info("Unable to save wallet"); + } + } + + public PirateWallet getCurrentWallet() { + return this.currentWallet; + } + + private void closeCurrentWallet() { + this.saveCurrentWallet(); + this.currentWallet = null; + } + + public void ensureInitialized() throws ForeignBlockchainException { + if (!LiteWalletJni.isLoaded() || this.currentWallet == null || !this.currentWallet.isInitialized()) { + throw new ForeignBlockchainException("Pirate wallet isn't initialized yet"); + } + } + + public void ensureNotNullSeed() throws ForeignBlockchainException { + // Safety check to make sure funds aren't sent to a null seed wallet + if (this.currentWallet == null || this.currentWallet.isNullSeedWallet()) { + throw new ForeignBlockchainException("Invalid wallet"); + } + } + + public void ensureSynchronized() throws ForeignBlockchainException { + if (this.currentWallet == null || !this.currentWallet.isSynchronized()) { + throw new ForeignBlockchainException("Wallet isn't synchronized yet"); + } + + String response = LiteWalletJni.execute("syncStatus", ""); + JSONObject json = new JSONObject(response); + if (json.has("syncing")) { + boolean isSyncing = Boolean.valueOf(json.getString("syncing")); + if (isSyncing) { + long syncedBlocks = json.getLong("synced_blocks"); + long totalBlocks = json.getLong("total_blocks"); + + throw new ForeignBlockchainException(String.format("Sync in progress (%d / %d). Please try again later.", syncedBlocks, totalBlocks)); + } + } + } + + public String getSyncStatus() { + if (this.currentWallet == null || !this.currentWallet.isInitialized()) { + if (this.loadStatus != null) { + return this.loadStatus; + } + + return "Not initialized yet"; + } + + String syncStatusResponse = LiteWalletJni.execute("syncStatus", ""); + org.json.JSONObject json = new JSONObject(syncStatusResponse); + if (json.has("syncing")) { + boolean isSyncing = Boolean.valueOf(json.getString("syncing")); + if (isSyncing) { + long syncedBlocks = json.getLong("synced_blocks"); + long totalBlocks = json.getLong("total_blocks"); + return String.format("Sync in progress (%d / %d)", syncedBlocks, totalBlocks); + } + } + + boolean isSynchronized = this.currentWallet.isSynchronized(); + if (isSynchronized) { + return "Synchronized"; + } + + return "Initializing wallet..."; + } + +} diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index 597752d2..2dad62e7 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -19,7 +19,6 @@ import org.qortal.block.BlockChain; import org.qortal.data.block.BlockData; import org.qortal.data.block.BlockSummaryData; import org.qortal.data.block.CommonBlockData; -import org.qortal.data.network.PeerChainTipData; import org.qortal.data.transaction.RewardShareTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.event.Event; @@ -54,7 +53,8 @@ public class Synchronizer extends Thread { /** Maximum number of block signatures we ask from peer in one go */ private static final int MAXIMUM_REQUEST_SIZE = 200; // XXX move to Settings? - private static final long RECOVERY_MODE_TIMEOUT = 10 * 60 * 1000L; // ms + /** Maximum number of consecutive failed sync attempts before marking peer as misbehaved */ + private static final int MAX_CONSECUTIVE_FAILED_SYNC_ATTEMPTS = 3; private boolean running; @@ -76,12 +76,14 @@ public class Synchronizer extends Thread { private volatile boolean isSynchronizing = false; /** Temporary estimate of synchronization progress for SysTray use. */ private volatile int syncPercent = 0; + /** Temporary estimate of blocks remaining for SysTray use. */ + private volatile int blocksRemaining = 0; private static volatile boolean requestSync = false; private boolean syncRequestPending = false; // Keep track of invalid blocks so that we don't keep trying to sync them - private Map invalidBlockSignatures = Collections.synchronizedMap(new HashMap<>()); + private Map invalidBlockSignatures = Collections.synchronizedMap(new HashMap<>()); public Long timeValidBlockLastReceived = null; public Long timeInvalidBlockLastReceived = null; @@ -181,6 +183,18 @@ public class Synchronizer extends Thread { } } + public Integer getBlocksRemaining() { + synchronized (this.syncLock) { + // Report as 0 blocks remaining if the latest block is within the last 60 mins + final Long minLatestBlockTimestamp = NTP.getTime() - (60 * 60 * 1000L); + if (Controller.getInstance().isUpToDate(minLatestBlockTimestamp)) { + return 0; + } + + return this.isSynchronizing ? this.blocksRemaining : null; + } + } + public void requestSync() { requestSync = true; } @@ -233,6 +247,9 @@ public class Synchronizer extends Thread { // Disregard peers that are on the same block as last sync attempt and we didn't like their chain peers.removeIf(Controller.hasInferiorChainTip); + // Disregard peers that have a block with an invalid signer + peers.removeIf(Controller.hasInvalidSigner); + final int peersBeforeComparison = peers.size(); // Request recent block summaries from the remaining peers, and locate our common block with each @@ -282,7 +299,7 @@ public class Synchronizer extends Thread { BlockData priorChainTip = Controller.getInstance().getChainTip(); synchronized (this.syncLock) { - this.syncPercent = (priorChainTip.getHeight() * 100) / peer.getChainTipData().getLastHeight(); + this.syncPercent = (priorChainTip.getHeight() * 100) / peer.getChainTipData().getHeight(); // Only update SysTray if we're potentially changing height if (this.syncPercent < 100) { @@ -312,7 +329,7 @@ public class Synchronizer extends Thread { case INFERIOR_CHAIN: { // Update our list of inferior chain tips - ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getLastBlockSignature()); + ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getSignature()); if (!inferiorChainSignatures.contains(inferiorChainSignature)) inferiorChainSignatures.add(inferiorChainSignature); @@ -320,7 +337,8 @@ public class Synchronizer extends Thread { LOGGER.debug(() -> String.format("Refused to synchronize with peer %s (%s)", peer, syncResult.name())); // Notify peer of our superior chain - if (!peer.sendMessage(Network.getInstance().buildHeightMessage(peer, priorChainTip))) + Message message = Network.getInstance().buildHeightOrChainTipInfo(peer); + if (message == null || !peer.sendMessage(message)) peer.disconnect("failed to notify peer of our superior chain"); break; } @@ -341,7 +359,7 @@ public class Synchronizer extends Thread { // fall-through... case NOTHING_TO_DO: { // Update our list of inferior chain tips - ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getLastBlockSignature()); + ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getSignature()); if (!inferiorChainSignatures.contains(inferiorChainSignature)) inferiorChainSignatures.add(inferiorChainSignature); @@ -369,8 +387,7 @@ public class Synchronizer extends Thread { // Reset our cache of inferior chains inferiorChainSignatures.clear(); - Network network = Network.getInstance(); - network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newChainTip)); + Network.getInstance().broadcastOurChain(); EventBus.INSTANCE.notify(new NewChainTipEvent(priorChainTip, newChainTip)); } @@ -397,9 +414,10 @@ public class Synchronizer extends Thread { timePeersLastAvailable = NTP.getTime(); // If enough time has passed, enter recovery mode, which lifts some restrictions on who we can sync with and when we can mint - if (NTP.getTime() - timePeersLastAvailable > RECOVERY_MODE_TIMEOUT) { + long recoveryModeTimeout = Settings.getInstance().getRecoveryModeTimeout(); + if (NTP.getTime() - timePeersLastAvailable > recoveryModeTimeout) { if (recoveryMode == false) { - LOGGER.info(String.format("Peers have been unavailable for %d minutes. Entering recovery mode...", RECOVERY_MODE_TIMEOUT/60/1000)); + LOGGER.info(String.format("Peers have been unavailable for %d minutes. Entering recovery mode...", recoveryModeTimeout/60/1000)); recoveryMode = true; } } @@ -513,13 +531,13 @@ public class Synchronizer extends Thread { final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock(); final int ourInitialHeight = ourLatestBlockData.getHeight(); - PeerChainTipData peerChainTipData = peer.getChainTipData(); - int peerHeight = peerChainTipData.getLastHeight(); - byte[] peersLastBlockSignature = peerChainTipData.getLastBlockSignature(); + BlockSummaryData peerChainTipData = peer.getChainTipData(); + int peerHeight = peerChainTipData.getHeight(); + byte[] peersLastBlockSignature = peerChainTipData.getSignature(); byte[] ourLastBlockSignature = ourLatestBlockData.getSignature(); LOGGER.debug(String.format("Fetching summaries from peer %s at height %d, sig %.8s, ts %d; our height %d, sig %.8s, ts %d", peer, - peerHeight, Base58.encode(peersLastBlockSignature), peer.getChainTipData().getLastBlockTimestamp(), + peerHeight, Base58.encode(peersLastBlockSignature), peerChainTipData.getTimestamp(), ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp())); List peerBlockSummaries = new ArrayList<>(); @@ -617,7 +635,7 @@ public class Synchronizer extends Thread { // We have already determined that the correct chain diverged from a lower height. We are safe to skip these peers. for (Peer peer : peersSharingCommonBlock) { LOGGER.debug(String.format("Peer %s has common block at height %d but the superior chain is at height %d. Removing it from this round.", peer, commonBlockSummary.getHeight(), dropPeersAfterCommonBlockHeight)); - this.addInferiorChainSignature(peer.getChainTipData().getLastBlockSignature()); + //this.addInferiorChainSignature(peer.getChainTipData().getLastBlockSignature()); } continue; } @@ -628,16 +646,18 @@ public class Synchronizer extends Thread { int minChainLength = this.calculateMinChainLengthOfPeers(peersSharingCommonBlock, commonBlockSummary); // Fetch block summaries from each peer - for (Peer peer : peersSharingCommonBlock) { + Iterator peersSharingCommonBlockIterator = peersSharingCommonBlock.iterator(); + while (peersSharingCommonBlockIterator.hasNext()) { + Peer peer = (Peer) peersSharingCommonBlockIterator.next(); // If we're shutting down, just return the latest peer list if (Controller.isStopping()) return peers; // Count the number of blocks this peer has beyond our common block - final PeerChainTipData peerChainTipData = peer.getChainTipData(); - final int peerHeight = peerChainTipData.getLastHeight(); - final byte[] peerLastBlockSignature = peerChainTipData.getLastBlockSignature(); + final BlockSummaryData peerChainTipData = peer.getChainTipData(); + final int peerHeight = peerChainTipData.getHeight(); + final byte[] peerLastBlockSignature = peerChainTipData.getSignature(); final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight(); // Limit the number of blocks we are comparing. FUTURE: we could request more in batches, but there may not be a case when this is needed int summariesRequired = Math.min(peerAdditionalBlocksAfterCommonBlock, MAXIMUM_REQUEST_SIZE); @@ -685,6 +705,8 @@ public class Synchronizer extends Thread { if (this.containsInvalidBlockSummary(peer.getCommonBlockData().getBlockSummariesAfterCommonBlock())) { LOGGER.debug("Ignoring peer %s because it holds an invalid block", peer); peers.remove(peer); + peersSharingCommonBlockIterator.remove(); + continue; } // Reduce minChainLength if needed. If we don't have any blocks, this peer will be excluded from chain weight comparisons later in the process, so we shouldn't update minChainLength @@ -723,8 +745,9 @@ public class Synchronizer extends Thread { LOGGER.debug(String.format("Listing peers with common block %.8s...", Base58.encode(commonBlockSummary.getSignature()))); for (Peer peer : peersSharingCommonBlock) { - final int peerHeight = peer.getChainTipData().getLastHeight(); - final Long peerLastBlockTimestamp = peer.getChainTipData().getLastBlockTimestamp(); + BlockSummaryData peerChainTipData = peer.getChainTipData(); + final int peerHeight = peerChainTipData.getHeight(); + final Long peerLastBlockTimestamp = peerChainTipData.getTimestamp(); final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight(); final CommonBlockData peerCommonBlockData = peer.getCommonBlockData(); @@ -821,7 +844,7 @@ public class Synchronizer extends Thread { // Calculate the length of the shortest peer chain sharing this common block int minChainLength = 0; for (Peer peer : peersSharingCommonBlock) { - final int peerHeight = peer.getChainTipData().getLastHeight(); + final int peerHeight = peer.getChainTipData().getHeight(); final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight(); if (peerAdditionalBlocksAfterCommonBlock < minChainLength || minChainLength == 0) @@ -840,6 +863,10 @@ public class Synchronizer extends Thread { /* Invalid block signature tracking */ + public Map getInvalidBlockSignatures() { + return this.invalidBlockSignatures; + } + private void addInvalidBlockSignature(byte[] signature) { Long now = NTP.getTime(); if (now == null) { @@ -847,8 +874,7 @@ public class Synchronizer extends Thread { } // Add or update existing entry - String sig58 = Base58.encode(signature); - invalidBlockSignatures.put(sig58, now); + invalidBlockSignatures.put(ByteArray.wrap(signature), now); } private void deleteOlderInvalidSignatures(Long now) { if (now == null) { @@ -867,17 +893,16 @@ public class Synchronizer extends Thread { } } } - private boolean containsInvalidBlockSummary(List blockSummaries) { + public boolean containsInvalidBlockSummary(List blockSummaries) { if (blockSummaries == null || invalidBlockSignatures == null) { return false; } // Loop through our known invalid blocks and check each one against supplied block summaries - for (String invalidSignature58 : invalidBlockSignatures.keySet()) { - byte[] invalidSignature = Base58.decode(invalidSignature58); + for (ByteArray invalidSignature : invalidBlockSignatures.keySet()) { for (BlockSummaryData blockSummary : blockSummaries) { byte[] signature = blockSummary.getSignature(); - if (Arrays.equals(signature, invalidSignature)) { + if (Arrays.equals(signature, invalidSignature.value)) { return true; } } @@ -890,10 +915,9 @@ public class Synchronizer extends Thread { } // Loop through our known invalid blocks and check each one against supplied block signatures - for (String invalidSignature58 : invalidBlockSignatures.keySet()) { - byte[] invalidSignature = Base58.decode(invalidSignature58); + for (ByteArray invalidSignature : invalidBlockSignatures.keySet()) { for (byte[] signature : blockSignatures) { - if (Arrays.equals(signature, invalidSignature)) { + if (Arrays.equals(signature, invalidSignature.value)) { return true; } } @@ -928,13 +952,13 @@ public class Synchronizer extends Thread { final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock(); final int ourInitialHeight = ourLatestBlockData.getHeight(); - PeerChainTipData peerChainTipData = peer.getChainTipData(); - int peerHeight = peerChainTipData.getLastHeight(); - byte[] peersLastBlockSignature = peerChainTipData.getLastBlockSignature(); + BlockSummaryData peerChainTipData = peer.getChainTipData(); + int peerHeight = peerChainTipData.getHeight(); + byte[] peersLastBlockSignature = peerChainTipData.getSignature(); byte[] ourLastBlockSignature = ourLatestBlockData.getSignature(); String syncString = String.format("Synchronizing with peer %s at height %d, sig %.8s, ts %d; our height %d, sig %.8s, ts %d", peer, - peerHeight, Base58.encode(peersLastBlockSignature), peer.getChainTipData().getLastBlockTimestamp(), + peerHeight, Base58.encode(peersLastBlockSignature), peerChainTipData.getTimestamp(), ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp()); LOGGER.info(syncString); @@ -1097,6 +1121,7 @@ public class Synchronizer extends Thread { // If common block is too far behind us then we're on massively different forks so give up. if (!force && testHeight < ourHeight - MAXIMUM_COMMON_DELTA) { LOGGER.info(String.format("Blockchain too divergent with peer %s", peer)); + peer.setLastTooDivergentTime(NTP.getTime()); return SynchronizationResult.TOO_DIVERGENT; } @@ -1106,6 +1131,9 @@ public class Synchronizer extends Thread { testHeight = Math.max(testHeight - step, 1); } + // Peer not considered too divergent + peer.setLastTooDivergentTime(0L); + // Prepend test block's summary as first block summary, as summaries returned are *after* test block BlockSummaryData testBlockSummary = new BlockSummaryData(testBlockData); blockSummariesFromCommon.add(0, testBlockSummary); @@ -1241,7 +1269,14 @@ public class Synchronizer extends Thread { int numberSignaturesRequired = additionalPeerBlocksAfterCommonBlock - peerBlockSignatures.size(); int retryCount = 0; - while (height < peerHeight) { + + // Keep fetching blocks from peer until we reach their tip, or reach a count of MAXIMUM_COMMON_DELTA blocks. + // We need to limit the total number, otherwise too much can be loaded into memory, causing an + // OutOfMemoryException. This is common when syncing from 1000+ blocks behind the chain tip, after starting + // from a small fork that didn't become part of the main chain. This causes the entire sync process to + // use syncToPeerChain(), resulting in potentially thousands of blocks being held in memory if the limit + // below isn't applied. + while (height < peerHeight && peerBlocks.size() <= MAXIMUM_COMMON_DELTA) { if (Controller.isStopping()) return SynchronizationResult.SHUTTING_DOWN; @@ -1308,7 +1343,7 @@ public class Synchronizer extends Thread { // Final check to make sure the peer isn't out of date (except for when we're in recovery mode) if (!recoveryMode && peer.getChainTipData() != null) { final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp(); - final Long peerLastBlockTimestamp = peer.getChainTipData().getLastBlockTimestamp(); + final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp(); if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) { LOGGER.info(String.format("Peer %s is out of date, so abandoning sync attempt", peer)); return SynchronizationResult.CHAIN_TIP_TOO_OLD; @@ -1443,6 +1478,12 @@ public class Synchronizer extends Thread { repository.saveChanges(); + synchronized (this.syncLock) { + if (peer.getChainTipData() != null) { + this.blocksRemaining = peer.getChainTipData().getHeight() - newBlock.getBlockData().getHeight(); + } + } + Controller.getInstance().onNewBlock(newBlock.getBlockData()); } @@ -1538,6 +1579,12 @@ public class Synchronizer extends Thread { repository.saveChanges(); + synchronized (this.syncLock) { + if (peer.getChainTipData() != null) { + this.blocksRemaining = peer.getChainTipData().getHeight() - newBlock.getBlockData().getHeight(); + } + } + Controller.getInstance().onNewBlock(newBlock.getBlockData()); } @@ -1548,12 +1595,19 @@ public class Synchronizer extends Thread { Message getBlockSummariesMessage = new GetBlockSummariesMessage(parentSignature, numberRequested); Message message = peer.getResponse(getBlockSummariesMessage); - if (message == null || message.getType() != MessageType.BLOCK_SUMMARIES) + if (message == null) return null; - BlockSummariesMessage blockSummariesMessage = (BlockSummariesMessage) message; + if (message.getType() == MessageType.BLOCK_SUMMARIES) { + BlockSummariesMessage blockSummariesMessage = (BlockSummariesMessage) message; + return blockSummariesMessage.getBlockSummaries(); + } + else if (message.getType() == MessageType.BLOCK_SUMMARIES_V2) { + BlockSummariesV2Message blockSummariesMessage = (BlockSummariesV2Message) message; + return blockSummariesMessage.getBlockSummaries(); + } - return blockSummariesMessage.getBlockSummaries(); + return null; } private List getBlockSignatures(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException { @@ -1572,8 +1626,20 @@ public class Synchronizer extends Thread { Message getBlockMessage = new GetBlockMessage(signature); Message message = peer.getResponse(getBlockMessage); - if (message == null) + if (message == null) { + peer.getPeerData().incrementFailedSyncCount(); + if (peer.getPeerData().getFailedSyncCount() >= MAX_CONSECUTIVE_FAILED_SYNC_ATTEMPTS) { + // Several failed attempts, so mark peer as misbehaved + LOGGER.info("Marking peer {} as misbehaved due to {} failed sync attempts", peer, peer.getPeerData().getFailedSyncCount()); + Network.getInstance().peerMisbehaved(peer); + } return null; + } + + // Reset failed sync count now that we have a block response + // FUTURE: we could move this to the end of the sync process, but to reduce risk this can be done + // at a later stage. For now we are only defending against serialization errors or no responses. + peer.getPeerData().setFailedSyncCount(0); switch (message.getType()) { case BLOCK: { diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java index 0a9f3ce1..63a6df80 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java @@ -67,6 +67,9 @@ public class ArbitraryDataFileListManager { /** Maximum number of hops that a file list relay request is allowed to make */ public static int RELAY_REQUEST_MAX_HOPS = 4; + /** Minimum peer version to use relay */ + public static String RELAY_MIN_PEER_VERSION = "3.4.0"; + private ArbitraryDataFileListManager() { } @@ -120,12 +123,22 @@ public class ArbitraryDataFileListManager { } } - // Then allow another 5 attempts, each 5 minutes apart + // Then allow another 3 attempts, each 5 minutes apart if (timeSinceLastAttempt > 5 * 60 * 1000L) { // We haven't tried for at least 5 minutes - if (networkBroadcastCount < 5) { - // We've made less than 5 total attempts + if (networkBroadcastCount < 6) { + // We've made less than 6 total attempts + return true; + } + } + + // Then allow another 4 attempts, each 30 minutes apart + if (timeSinceLastAttempt > 30 * 60 * 1000L) { + // We haven't tried for at least 5 minutes + + if (networkBroadcastCount < 10) { + // We've made less than 10 total attempts return true; } } @@ -184,8 +197,8 @@ public class ArbitraryDataFileListManager { } } - if (timeSinceLastAttempt > 24 * 60 * 60 * 1000L) { - // We haven't tried for at least 24 hours + if (timeSinceLastAttempt > 60 * 60 * 1000L) { + // We haven't tried for at least 1 hour return true; } @@ -524,6 +537,7 @@ public class ArbitraryDataFileListManager { forwardArbitraryDataFileListMessage = new ArbitraryDataFileListMessage(signature, hashes, requestTime, requestHops, arbitraryDataFileListMessage.getPeerAddress(), arbitraryDataFileListMessage.isRelayPossible()); } + forwardArbitraryDataFileListMessage.setId(message.getId()); // Forward to requesting peer LOGGER.debug("Forwarding file list with {} hashes to requesting peer: {}", hashes.size(), requestingPeer); @@ -694,9 +708,10 @@ public class ArbitraryDataFileListManager { LOGGER.debug("Rebroadcasting hash list request from peer {} for signature {} to our other peers... totalRequestTime: {}, requestHops: {}", peer, Base58.encode(signature), totalRequestTime, requestHops); Network.getInstance().broadcast( - broadcastPeer -> broadcastPeer == peer || - Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost()) - ? null : relayGetArbitraryDataFileListMessage); + broadcastPeer -> + !broadcastPeer.isAtLeastVersion(RELAY_MIN_PEER_VERSION) ? null : + broadcastPeer == peer || Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost()) ? null : relayGetArbitraryDataFileListMessage + ); } else { diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java index 22cf4144..e2de1ae0 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java @@ -82,7 +82,7 @@ public class ArbitraryDataFileManager extends Thread { try { // Use a fixed thread pool to execute the arbitrary data file requests - int threadCount = 10; + int threadCount = 5; ExecutorService arbitraryDataFileRequestExecutor = Executors.newFixedThreadPool(threadCount); for (int i = 0; i < threadCount; i++) { arbitraryDataFileRequestExecutor.execute(new ArbitraryDataFileRequestThread()); @@ -288,7 +288,7 @@ public class ArbitraryDataFileManager extends Thread { // The ID needs to match that of the original request message.setId(originalMessage.getId()); - if (!requestingPeer.sendMessage(message)) { + if (!requestingPeer.sendMessageWithTimeout(message, (int) ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT)) { LOGGER.debug("Failed to forward arbitrary data file to peer {}", requestingPeer); requestingPeer.disconnect("failed to forward arbitrary data file"); } @@ -564,13 +564,16 @@ public class ArbitraryDataFileManager extends Thread { LOGGER.trace("Hash {} exists", hash58); // We can serve the file directly as we already have it + LOGGER.debug("Sending file {}...", arbitraryDataFile); ArbitraryDataFileMessage arbitraryDataFileMessage = new ArbitraryDataFileMessage(signature, arbitraryDataFile); arbitraryDataFileMessage.setId(message.getId()); - if (!peer.sendMessage(arbitraryDataFileMessage)) { - LOGGER.debug("Couldn't sent file"); + if (!peer.sendMessageWithTimeout(arbitraryDataFileMessage, (int) ArbitraryDataManager.ARBITRARY_REQUEST_TIMEOUT)) { + LOGGER.debug("Couldn't send file {}", arbitraryDataFile); peer.disconnect("failed to send file"); } - LOGGER.debug("Sent file {}", arbitraryDataFile); + else { + LOGGER.debug("Sent file {}", arbitraryDataFile); + } } else if (relayInfo != null) { LOGGER.debug("We have relay info for hash {}", Base58.encode(hash)); @@ -595,9 +598,10 @@ public class ArbitraryDataFileManager extends Thread { // Send valid, yet unexpected message type in response, so peer's synchronizer doesn't have to wait for timeout LOGGER.debug(String.format("Sending 'file unknown' response to peer %s for GET_FILE request for unknown file %s", peer, arbitraryDataFile)); - // We'll send empty block summaries message as it's very short - // TODO: use a different message type here - Message fileUnknownMessage = new BlockSummariesMessage(Collections.emptyList()); + // Send generic 'unknown' message as it's very short + Message fileUnknownMessage = peer.getPeersVersion() >= GenericUnknownMessage.MINIMUM_PEER_VERSION + ? new GenericUnknownMessage() + : new BlockSummariesMessage(Collections.emptyList()); fileUnknownMessage.setId(message.getId()); if (!peer.sendMessage(fileUnknownMessage)) { LOGGER.debug("Couldn't sent file-unknown response"); diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java index 4568d3fd..ededbfa6 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java @@ -48,7 +48,6 @@ public class ArbitraryDataStorageManager extends Thread { private List hostedTransactions; private String searchQuery; - private List searchResultsTransactions; private static final long DIRECTORY_SIZE_CHECK_INTERVAL = 10 * 60 * 1000L; // 10 minutes @@ -344,11 +343,6 @@ public class ArbitraryDataStorageManager extends Thread { */ public List searchHostedTransactions(Repository repository, String query, Integer limit, Integer offset) { - // Load from results cache if we can (results that exists for the same query), to avoid disk reads - if (this.searchResultsTransactions != null && this.searchQuery.equals(query.toLowerCase())) { - return ArbitraryTransactionUtils.limitOffsetTransactions(this.searchResultsTransactions, limit, offset); - } - // Using cache if we can, to avoid disk reads if (this.hostedTransactions == null) { this.hostedTransactions = this.loadAllHostedTransactions(repository); @@ -376,10 +370,7 @@ public class ArbitraryDataStorageManager extends Thread { // Sort by newest first searchResultsList.sort(Comparator.comparingLong(ArbitraryTransactionData::getTimestamp).reversed()); - // Update cache - this.searchResultsTransactions = searchResultsList; - - return ArbitraryTransactionUtils.limitOffsetTransactions(this.searchResultsTransactions, limit, offset); + return ArbitraryTransactionUtils.limitOffsetTransactions(searchResultsList, limit, offset); } /** diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java index 9413b316..eec0d935 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java @@ -22,8 +22,7 @@ import org.qortal.utils.Triple; import java.io.IOException; import java.util.*; -import static org.qortal.controller.arbitrary.ArbitraryDataFileListManager.RELAY_REQUEST_MAX_DURATION; -import static org.qortal.controller.arbitrary.ArbitraryDataFileListManager.RELAY_REQUEST_MAX_HOPS; +import static org.qortal.controller.arbitrary.ArbitraryDataFileListManager.*; public class ArbitraryMetadataManager { @@ -435,12 +434,13 @@ public class ArbitraryMetadataManager { // Relay request hasn't reached the maximum number of hops yet, so can be rebroadcast Message relayGetArbitraryMetadataMessage = new GetArbitraryMetadataMessage(signature, requestTime, requestHops); + relayGetArbitraryMetadataMessage.setId(message.getId()); LOGGER.debug("Rebroadcasting metadata request from peer {} for signature {} to our other peers... totalRequestTime: {}, requestHops: {}", peer, Base58.encode(signature), totalRequestTime, requestHops); Network.getInstance().broadcast( - broadcastPeer -> broadcastPeer == peer || - Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost()) - ? null : relayGetArbitraryMetadataMessage); + broadcastPeer -> + !broadcastPeer.isAtLeastVersion(RELAY_MIN_PEER_VERSION) ? null : + broadcastPeer == peer || Objects.equals(broadcastPeer.getPeerData().getAddress().getHost(), peer.getPeerData().getAddress().getHost()) ? null : relayGetArbitraryMetadataMessage); } else { diff --git a/src/main/java/org/qortal/controller/repository/AtStatesPruner.java b/src/main/java/org/qortal/controller/repository/AtStatesPruner.java index bd12f784..064fe0ea 100644 --- a/src/main/java/org/qortal/controller/repository/AtStatesPruner.java +++ b/src/main/java/org/qortal/controller/repository/AtStatesPruner.java @@ -42,6 +42,7 @@ public class AtStatesPruner implements Runnable { repository.discardChanges(); repository.getATRepository().rebuildLatestAtStates(); + repository.saveChanges(); while (!Controller.isStopping()) { repository.discardChanges(); diff --git a/src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java b/src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java index 69fa347c..6c026385 100644 --- a/src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java +++ b/src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java @@ -29,6 +29,7 @@ public class AtStatesTrimmer implements Runnable { repository.discardChanges(); repository.getATRepository().rebuildLatestAtStates(); + repository.saveChanges(); while (!Controller.isStopping()) { repository.discardChanges(); diff --git a/src/main/java/org/qortal/controller/repository/BlockArchiver.java b/src/main/java/org/qortal/controller/repository/BlockArchiver.java index 8757bf32..63d61ef8 100644 --- a/src/main/java/org/qortal/controller/repository/BlockArchiver.java +++ b/src/main/java/org/qortal/controller/repository/BlockArchiver.java @@ -16,7 +16,7 @@ public class BlockArchiver implements Runnable { private static final Logger LOGGER = LogManager.getLogger(BlockArchiver.class); - private static final long INITIAL_SLEEP_PERIOD = 0L; // TODO: 5 * 60 * 1000L + 1234L; // ms + private static final long INITIAL_SLEEP_PERIOD = 5 * 60 * 1000L + 1234L; // ms public void run() { Thread.currentThread().setName("Block archiver"); diff --git a/src/main/java/org/qortal/controller/repository/NamesDatabaseIntegrityCheck.java b/src/main/java/org/qortal/controller/repository/NamesDatabaseIntegrityCheck.java index e69d1a35..004fa692 100644 --- a/src/main/java/org/qortal/controller/repository/NamesDatabaseIntegrityCheck.java +++ b/src/main/java/org/qortal/controller/repository/NamesDatabaseIntegrityCheck.java @@ -102,6 +102,21 @@ public class NamesDatabaseIntegrityCheck { } } + // Process CANCEL_SELL_NAME transactions + if (currentTransaction.getType() == TransactionType.CANCEL_SELL_NAME) { + CancelSellNameTransactionData cancelSellNameTransactionData = (CancelSellNameTransactionData) currentTransaction; + Name nameObj = new Name(repository, cancelSellNameTransactionData.getName()); + if (nameObj != null && nameObj.getNameData() != null) { + nameObj.cancelSell(cancelSellNameTransactionData); + modificationCount++; + LOGGER.trace("Processed CANCEL_SELL_NAME transaction for name {}", name); + } + else { + // Something went wrong + throw new DataException(String.format("Name data not found for name %s", cancelSellNameTransactionData.getName())); + } + } + // Process BUY_NAME transactions if (currentTransaction.getType() == TransactionType.BUY_NAME) { BuyNameTransactionData buyNameTransactionData = (BuyNameTransactionData) currentTransaction; @@ -128,7 +143,7 @@ public class NamesDatabaseIntegrityCheck { public int rebuildAllNames() { int modificationCount = 0; try (final Repository repository = RepositoryManager.getRepository()) { - List names = this.fetchAllNames(repository); + List names = this.fetchAllNames(repository); // TODO: de-duplicate, to speed up this process for (String name : names) { modificationCount += this.rebuildName(name, repository); } @@ -326,6 +341,10 @@ public class NamesDatabaseIntegrityCheck { TransactionType.BUY_NAME, Arrays.asList("name = ?"), Arrays.asList(name)); signatures.addAll(buyNameTransactions); + List cancelSellNameTransactions = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria( + TransactionType.CANCEL_SELL_NAME, Arrays.asList("name = ?"), Arrays.asList(name)); + signatures.addAll(cancelSellNameTransactions); + List transactions = new ArrayList<>(); for (byte[] signature : signatures) { TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); @@ -390,6 +409,12 @@ public class NamesDatabaseIntegrityCheck { names.add(sellNameTransactionData.getName()); } } + if ((transactionData instanceof CancelSellNameTransactionData)) { + CancelSellNameTransactionData cancelSellNameTransactionData = (CancelSellNameTransactionData) transactionData; + if (!names.contains(cancelSellNameTransactionData.getName())) { + names.add(cancelSellNameTransactionData.getName()); + } + } } return names; } diff --git a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv2TradeBot.java b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv2TradeBot.java deleted file mode 100644 index 6261339a..00000000 --- a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv2TradeBot.java +++ /dev/null @@ -1,885 +0,0 @@ -package org.qortal.controller.tradebot; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.bitcoinj.core.*; -import org.bitcoinj.script.Script.ScriptType; -import org.qortal.account.PrivateKeyAccount; -import org.qortal.account.PublicKeyAccount; -import org.qortal.api.model.crosschain.TradeBotCreateRequest; -import org.qortal.asset.Asset; -import org.qortal.crosschain.*; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.crosschain.TradeBotData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.group.Group; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.transaction.DeployAtTransaction; -import org.qortal.transaction.MessageTransaction; -import org.qortal.transaction.Transaction.ValidationResult; -import org.qortal.transform.TransformationException; -import org.qortal.transform.transaction.DeployAtTransactionTransformer; -import org.qortal.utils.Base58; -import org.qortal.utils.NTP; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.toMap; - -/** - * Performing cross-chain trading steps on behalf of user. - *

- * We deal with three different independent state-spaces here: - *

    - *
  • Qortal blockchain
  • - *
  • Foreign blockchain
  • - *
  • Trade-bot entries
  • - *
- */ -public class LitecoinACCTv2TradeBot implements AcctTradeBot { - - private static final Logger LOGGER = LogManager.getLogger(LitecoinACCTv2TradeBot.class); - - public enum State implements TradeBot.StateNameAndValueSupplier { - BOB_WAITING_FOR_AT_CONFIRM(10, false, false), - BOB_WAITING_FOR_MESSAGE(15, true, true), - BOB_WAITING_FOR_AT_REDEEM(25, true, true), - BOB_DONE(30, false, false), - BOB_REFUNDED(35, false, false), - - ALICE_WAITING_FOR_AT_LOCK(85, true, true), - ALICE_DONE(95, false, false), - ALICE_REFUNDING_A(105, true, true), - ALICE_REFUNDED(110, false, false); - - private static final Map map = stream(State.values()).collect(toMap(state -> state.value, state -> state)); - - public final int value; - public final boolean requiresAtData; - public final boolean requiresTradeData; - - State(int value, boolean requiresAtData, boolean requiresTradeData) { - this.value = value; - this.requiresAtData = requiresAtData; - this.requiresTradeData = requiresTradeData; - } - - public static State valueOf(int value) { - return map.get(value); - } - - @Override - public String getState() { - return this.name(); - } - - @Override - public int getStateValue() { - return this.value; - } - } - - /** Maximum time Bob waits for his AT creation transaction to be confirmed into a block. (milliseconds) */ - private static final long MAX_AT_CONFIRMATION_PERIOD = 24 * 60 * 60 * 1000L; // ms - - private static LitecoinACCTv2TradeBot instance; - - private final List endStates = Arrays.asList(State.BOB_DONE, State.BOB_REFUNDED, State.ALICE_DONE, State.ALICE_REFUNDING_A, State.ALICE_REFUNDED).stream() - .map(State::name) - .collect(Collectors.toUnmodifiableList()); - - private LitecoinACCTv2TradeBot() { - } - - public static synchronized LitecoinACCTv2TradeBot getInstance() { - if (instance == null) - instance = new LitecoinACCTv2TradeBot(); - - return instance; - } - - @Override - public List getEndStates() { - return this.endStates; - } - - /** - * Creates a new trade-bot entry from the "Bob" viewpoint, i.e. OFFERing QORT in exchange for LTC. - *

- * Generates: - *

    - *
  • new 'trade' private key
  • - *
- * Derives: - *
    - *
  • 'native' (as in Qortal) public key, public key hash, address (starting with Q)
  • - *
  • 'foreign' (as in Litecoin) public key, public key hash
  • - *
- * A Qortal AT is then constructed including the following as constants in the 'data segment': - *
    - *
  • 'native'/Qortal 'trade' address - used as a MESSAGE contact
  • - *
  • 'foreign'/Litecoin public key hash - used by Alice's P2SH scripts to allow redeem
  • - *
  • QORT amount on offer by Bob
  • - *
  • LTC amount expected in return by Bob (from Alice)
  • - *
  • trading timeout, in case things go wrong and everyone needs to refund
  • - *
- * Returns a DEPLOY_AT transaction that needs to be signed and broadcast to the Qortal network. - *

- * Trade-bot will wait for Bob's AT to be deployed before taking next step. - *

- * @param repository - * @param tradeBotCreateRequest - * @return raw, unsigned DEPLOY_AT transaction - * @throws DataException - */ - public byte[] createTrade(Repository repository, TradeBotCreateRequest tradeBotCreateRequest) throws DataException { - byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); - - byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey); - byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey); - String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey); - - byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); - byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); - - // Convert Litecoin receiving address into public key hash (we only support P2PKH at this time) - Address litecoinReceivingAddress; - try { - litecoinReceivingAddress = Address.fromString(Litecoin.getInstance().getNetworkParameters(), tradeBotCreateRequest.receivingAddress); - } catch (AddressFormatException e) { - throw new DataException("Unsupported Litecoin receiving address: " + tradeBotCreateRequest.receivingAddress); - } - if (litecoinReceivingAddress.getOutputScriptType() != ScriptType.P2PKH) - throw new DataException("Unsupported Litecoin receiving address: " + tradeBotCreateRequest.receivingAddress); - - byte[] litecoinReceivingAccountInfo = litecoinReceivingAddress.getHash(); - - PublicKeyAccount creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey); - - // Deploy AT - long timestamp = NTP.getTime(); - byte[] reference = creator.getLastReference(); - long fee = 0L; - byte[] signature = null; - BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, creator.getPublicKey(), fee, signature); - - String name = "QORT/LTC ACCT"; - String description = "QORT/LTC cross-chain trade"; - String aTType = "ACCT"; - String tags = "ACCT QORT LTC"; - byte[] creationBytes = LitecoinACCTv2.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, tradeBotCreateRequest.qortAmount, - tradeBotCreateRequest.foreignAmount, tradeBotCreateRequest.tradeTimeout); - long amount = tradeBotCreateRequest.fundingQortAmount; - - DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - DeployAtTransaction.ensureATAddress(deployAtTransactionData); - String atAddress = deployAtTransactionData.getAtAddress(); - - TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, LitecoinACCTv2.NAME, - State.BOB_WAITING_FOR_AT_CONFIRM.name(), State.BOB_WAITING_FOR_AT_CONFIRM.value, - creator.getAddress(), atAddress, timestamp, tradeBotCreateRequest.qortAmount, - tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, - null, null, - SupportedBlockchain.LITECOIN.name(), - tradeForeignPublicKey, tradeForeignPublicKeyHash, - tradeBotCreateRequest.foreignAmount, null, null, null, litecoinReceivingAccountInfo); - - TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Built AT %s. Waiting for deployment", atAddress)); - - // Attempt to backup the trade bot data - TradeBot.backupTradeBotData(repository, null); - - // Return to user for signing and broadcast as we don't have their Qortal private key - try { - return DeployAtTransactionTransformer.toBytes(deployAtTransactionData); - } catch (TransformationException e) { - throw new DataException("Failed to transform DEPLOY_AT transaction?", e); - } - } - - /** - * Creates a trade-bot entry from the 'Alice' viewpoint, i.e. matching LTC to an existing offer. - *

- * Requires a chosen trade offer from Bob, passed by crossChainTradeData - * and access to a Litecoin wallet via xprv58. - *

- * The crossChainTradeData contains the current trade offer state - * as extracted from the AT's data segment. - *

- * Access to a funded wallet is via a Litecoin BIP32 hierarchical deterministic key, - * passed via xprv58. - * This key will be stored in your node's database - * to allow trade-bot to create/fund the necessary P2SH transactions! - * However, due to the nature of BIP32 keys, it is possible to give the trade-bot - * only a subset of wallet access (see BIP32 for more details). - *

- * As an example, the xprv58 can be extract from a legacy, password-less - * Electrum wallet by going to the console tab and entering:
- * wallet.keystore.xprv
- * which should result in a base58 string starting with either 'xprv' (for Litecoin main-net) - * or 'tprv' for (Litecoin test-net). - *

- * It is envisaged that the value in xprv58 will actually come from a Qortal-UI-managed wallet. - *

- * If sufficient funds are available, this method will actually fund the P2SH-A - * with the Litecoin amount expected by 'Bob'. - *

- * If the Litecoin transaction is successfully broadcast to the network then - * we also send a MESSAGE to Bob's trade-bot to let them know. - *

- * The trade-bot entry is saved to the repository and the cross-chain trading process commences. - *

- * @param repository - * @param crossChainTradeData chosen trade OFFER that Alice wants to match - * @param xprv58 funded wallet xprv in base58 - * @return true if P2SH-A funding transaction successfully broadcast to Litecoin network, false otherwise - * @throws DataException - */ - public ResponseResult startResponse(Repository repository, ATData atData, ACCT acct, CrossChainTradeData crossChainTradeData, String xprv58, String receivingAddress) throws DataException { - byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); - byte[] secretA = TradeBot.generateSecret(); - byte[] hashOfSecretA = Crypto.hash160(secretA); - - byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey); - byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey); - String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey); - - byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); - byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); - byte[] receivingPublicKeyHash = Base58.decode(receivingAddress); // Actually the whole address, not just PKH - - // We need to generate lockTime-A: add tradeTimeout to now - long now = NTP.getTime(); - int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (now / 1000L); - - TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, LitecoinACCTv2.NAME, - State.ALICE_WAITING_FOR_AT_LOCK.name(), State.ALICE_WAITING_FOR_AT_LOCK.value, - receivingAddress, crossChainTradeData.qortalAtAddress, now, crossChainTradeData.qortAmount, - tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, - secretA, hashOfSecretA, - SupportedBlockchain.LITECOIN.name(), - tradeForeignPublicKey, tradeForeignPublicKeyHash, - crossChainTradeData.expectedForeignAmount, xprv58, null, lockTimeA, receivingPublicKeyHash); - - // Attempt to backup the trade bot data - // Include tradeBotData as an additional parameter, since it's not in the repository yet - TradeBot.backupTradeBotData(repository, Arrays.asList(tradeBotData)); - - // Check we have enough funds via xprv58 to fund P2SH to cover expectedForeignAmount - long p2shFee; - try { - p2shFee = Litecoin.getInstance().getP2shFee(now); - } catch (ForeignBlockchainException e) { - LOGGER.debug("Couldn't estimate Litecoin fees?"); - return ResponseResult.NETWORK_ISSUE; - } - - // Fee for redeem/refund is subtracted from P2SH-A balance. - // Do not include fee for funding transaction as this is covered by buildSpend() - long amountA = crossChainTradeData.expectedForeignAmount + p2shFee /*redeeming/refunding P2SH-A*/; - - // P2SH-A to be funded - byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(tradeForeignPublicKeyHash, lockTimeA, crossChainTradeData.creatorForeignPKH, hashOfSecretA); - String p2shAddress = Litecoin.getInstance().deriveP2shAddress(redeemScriptBytes); - - // Build transaction for funding P2SH-A - Transaction p2shFundingTransaction = Litecoin.getInstance().buildSpend(tradeBotData.getForeignKey(), p2shAddress, amountA); - if (p2shFundingTransaction == null) { - LOGGER.debug("Unable to build P2SH-A funding transaction - lack of funds?"); - return ResponseResult.BALANCE_ISSUE; - } - - try { - Litecoin.getInstance().broadcastTransaction(p2shFundingTransaction); - } catch (ForeignBlockchainException e) { - // We couldn't fund P2SH-A at this time - LOGGER.debug("Couldn't broadcast P2SH-A funding transaction?"); - return ResponseResult.NETWORK_ISSUE; - } - - // Attempt to send MESSAGE to Bob's Qortal trade address - byte[] messageData = LitecoinACCTv2.buildOfferMessage(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getHashOfSecret(), tradeBotData.getLockTimeA()); - String messageRecipient = crossChainTradeData.qortalCreatorTradeAddress; - - boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); - if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - - messageTransaction.computeNonce(); - messageTransaction.sign(sender); - - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } - } - - TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); - - return ResponseResult.OK; - } - - @Override - public boolean canDelete(Repository repository, TradeBotData tradeBotData) throws DataException { - State tradeBotState = State.valueOf(tradeBotData.getStateValue()); - if (tradeBotState == null) - return true; - - // If the AT doesn't exist then we might as well let the user tidy up - if (!repository.getATRepository().exists(tradeBotData.getAtAddress())) - return true; - - switch (tradeBotState) { - case BOB_WAITING_FOR_AT_CONFIRM: - case ALICE_DONE: - case BOB_DONE: - case ALICE_REFUNDED: - case BOB_REFUNDED: - case ALICE_REFUNDING_A: - return true; - - default: - return false; - } - } - - @Override - public void progress(Repository repository, TradeBotData tradeBotData) throws DataException, ForeignBlockchainException { - State tradeBotState = State.valueOf(tradeBotData.getStateValue()); - if (tradeBotState == null) { - LOGGER.info(() -> String.format("Trade-bot entry for AT %s has invalid state?", tradeBotData.getAtAddress())); - return; - } - - ATData atData = null; - CrossChainTradeData tradeData = null; - - if (tradeBotState.requiresAtData) { - // Attempt to fetch AT data - atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress()); - if (atData == null) { - LOGGER.debug(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress())); - return; - } - - if (tradeBotState.requiresTradeData) { - tradeData = LitecoinACCTv2.getInstance().populateTradeData(repository, atData); - if (tradeData == null) { - LOGGER.warn(() -> String.format("Unable to fetch ACCT trade data for AT %s from repository", tradeBotData.getAtAddress())); - return; - } - } - } - - switch (tradeBotState) { - case BOB_WAITING_FOR_AT_CONFIRM: - handleBobWaitingForAtConfirm(repository, tradeBotData); - break; - - case BOB_WAITING_FOR_MESSAGE: - TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData); - handleBobWaitingForMessage(repository, tradeBotData, atData, tradeData); - break; - - case ALICE_WAITING_FOR_AT_LOCK: - TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData); - handleAliceWaitingForAtLock(repository, tradeBotData, atData, tradeData); - break; - - case BOB_WAITING_FOR_AT_REDEEM: - TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData); - handleBobWaitingForAtRedeem(repository, tradeBotData, atData, tradeData); - break; - - case ALICE_DONE: - case BOB_DONE: - break; - - case ALICE_REFUNDING_A: - TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData); - handleAliceRefundingP2shA(repository, tradeBotData, atData, tradeData); - break; - - case ALICE_REFUNDED: - case BOB_REFUNDED: - break; - } - } - - /** - * Trade-bot is waiting for Bob's AT to deploy. - *

- * If AT is deployed, then trade-bot's next step is to wait for MESSAGE from Alice. - */ - private void handleBobWaitingForAtConfirm(Repository repository, TradeBotData tradeBotData) throws DataException { - if (!repository.getATRepository().exists(tradeBotData.getAtAddress())) { - if (NTP.getTime() - tradeBotData.getTimestamp() <= MAX_AT_CONFIRMATION_PERIOD) - return; - - // We've waited ages for AT to be confirmed into a block but something has gone awry. - // After this long we assume transaction loss so give up with trade-bot entry too. - tradeBotData.setState(State.BOB_REFUNDED.name()); - tradeBotData.setStateValue(State.BOB_REFUNDED.value); - tradeBotData.setTimestamp(NTP.getTime()); - // We delete trade-bot entry here instead of saving, hence not using updateTradeBotState() - repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey()); - repository.saveChanges(); - - LOGGER.info(() -> String.format("AT %s never confirmed. Giving up on trade", tradeBotData.getAtAddress())); - TradeBot.notifyStateChange(tradeBotData); - return; - } - - TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_WAITING_FOR_MESSAGE, - () -> String.format("AT %s confirmed ready. Waiting for trade message", tradeBotData.getAtAddress())); - } - - /** - * Trade-bot is waiting for MESSAGE from Alice's trade-bot, containing Alice's trade info. - *

- * It's possible Bob has cancelling his trade offer, receiving an automatic QORT refund, - * in which case trade-bot is done with this specific trade and finalizes on refunded state. - *

- * Assuming trade is still on offer, trade-bot checks the contents of MESSAGE from Alice's trade-bot. - *

- * Details from Alice are used to derive P2SH-A address and this is checked for funding balance. - *

- * Assuming P2SH-A has at least expected Litecoin balance, - * Bob's trade-bot constructs a zero-fee, PoW MESSAGE to send to Bob's AT with more trade details. - *

- * On processing this MESSAGE, Bob's AT should switch into 'TRADE' mode and only trade with Alice. - *

- * Trade-bot's next step is to wait for Alice to redeem the AT, which will allow Bob to - * extract secret-A needed to redeem Alice's P2SH. - * @throws ForeignBlockchainException - */ - private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData, - ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException { - // If AT has finished then Bob likely cancelled his trade offer - if (atData.getIsFinished()) { - TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_REFUNDED, - () -> String.format("AT %s cancelled - trading aborted", tradeBotData.getAtAddress())); - return; - } - - Litecoin litecoin = Litecoin.getInstance(); - - String address = tradeBotData.getTradeNativeAddress(); - List messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, address, null, null, null); - - for (MessageTransactionData messageTransactionData : messageTransactionsData) { - if (messageTransactionData.isText()) - continue; - - // We're expecting: HASH160(secret-A), Alice's Litecoin pubkeyhash and lockTime-A - byte[] messageData = messageTransactionData.getData(); - LitecoinACCTv2.OfferMessageData offerMessageData = LitecoinACCTv2.extractOfferMessageData(messageData); - if (offerMessageData == null) - continue; - - byte[] aliceForeignPublicKeyHash = offerMessageData.partnerLitecoinPKH; - byte[] hashOfSecretA = offerMessageData.hashOfSecretA; - int lockTimeA = (int) offerMessageData.lockTimeA; - long messageTimestamp = messageTransactionData.getTimestamp(); - int refundTimeout = LitecoinACCTv2.calcRefundTimeout(messageTimestamp, lockTimeA); - - // Determine P2SH-A address and confirm funded - byte[] redeemScriptA = BitcoinyHTLC.buildScript(aliceForeignPublicKeyHash, lockTimeA, tradeBotData.getTradeForeignPublicKeyHash(), hashOfSecretA); - String p2shAddressA = litecoin.deriveP2shAddress(redeemScriptA); - - long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); - long p2shFee = Litecoin.getInstance().getP2shFee(feeTimestamp); - final long minimumAmountA = tradeBotData.getForeignAmount() + p2shFee; - - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); - - switch (htlcStatusA) { - case UNFUNDED: - case FUNDING_IN_PROGRESS: - // There might be another MESSAGE from someone else with an actually funded P2SH-A... - continue; - - case REDEEM_IN_PROGRESS: - case REDEEMED: - // We've already redeemed this? - TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_DONE, - () -> String.format("P2SH-A %s already spent? Assuming trade complete", p2shAddressA)); - return; - - case REFUND_IN_PROGRESS: - case REFUNDED: - // This P2SH-A is burnt, but there might be another MESSAGE from someone else with an actually funded P2SH-A... - continue; - - case FUNDED: - // Fall-through out of switch... - break; - } - - // Good to go - send MESSAGE to AT - - String aliceNativeAddress = Crypto.toAddress(messageTransactionData.getCreatorPublicKey()); - - // Build outgoing message, padding each part to 32 bytes to make it easier for AT to consume - byte[] outgoingMessageData = LitecoinACCTv2.buildTradeMessage(aliceNativeAddress, aliceForeignPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - String messageRecipient = tradeBotData.getAtAddress(); - - boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, outgoingMessageData); - if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false); - - outgoingMessageTransaction.computeNonce(); - outgoingMessageTransaction.sign(sender); - - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); - - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); - return; - } - } - - TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_WAITING_FOR_AT_REDEEM, - () -> String.format("Locked AT %s to %s. Waiting for AT redeem", tradeBotData.getAtAddress(), aliceNativeAddress)); - - return; - } - } - - /** - * Trade-bot is waiting for Bob's AT to switch to TRADE mode and lock trade to Alice only. - *

- * It's possible that Bob has cancelled his trade offer in the mean time, or that somehow - * this process has taken so long that we've reached P2SH-A's locktime, or that someone else - * has managed to trade with Bob. In any of these cases, trade-bot switches to begin the refunding process. - *

- * Assuming Bob's AT is locked to Alice, trade-bot checks AT's state data to make sure it is correct. - *

- * If all is well, trade-bot then redeems AT using Alice's secret-A, releasing Bob's QORT to Alice. - *

- * In revealing a valid secret-A, Bob can then redeem the LTC funds from P2SH-A. - *

- * @throws ForeignBlockchainException - */ - private void handleAliceWaitingForAtLock(Repository repository, TradeBotData tradeBotData, - ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException { - if (aliceUnexpectedState(repository, tradeBotData, atData, crossChainTradeData)) - return; - - Litecoin litecoin = Litecoin.getInstance(); - int lockTimeA = tradeBotData.getLockTimeA(); - - // Refund P2SH-A if we've passed lockTime-A - if (NTP.getTime() >= lockTimeA * 1000L) { - byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); - String p2shAddressA = litecoin.deriveP2shAddress(redeemScriptA); - - long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); - long p2shFee = Litecoin.getInstance().getP2shFee(feeTimestamp); - long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee; - - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); - - switch (htlcStatusA) { - case UNFUNDED: - case FUNDING_IN_PROGRESS: - case FUNDED: - break; - - case REDEEM_IN_PROGRESS: - case REDEEMED: - // Already redeemed? - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE, - () -> String.format("P2SH-A %s already spent? Assuming trade completed", p2shAddressA)); - return; - - case REFUND_IN_PROGRESS: - case REFUNDED: - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED, - () -> String.format("P2SH-A %s already refunded. Trade aborted", p2shAddressA)); - return; - - } - - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A, - () -> atData.getIsFinished() - ? String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddressA) - : String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddressA)); - - return; - } - - // We're waiting for AT to be in TRADE mode - if (crossChainTradeData.mode != AcctMode.TRADING) - return; - - // AT is in TRADE mode and locked to us as checked by aliceUnexpectedState() above - - // Find our MESSAGE to AT from previous state - List messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(tradeBotData.getTradeNativePublicKey(), - crossChainTradeData.qortalCreatorTradeAddress, null, null, null); - if (messageTransactionsData == null || messageTransactionsData.isEmpty()) { - LOGGER.warn(() -> String.format("Unable to find our message to trade creator %s?", crossChainTradeData.qortalCreatorTradeAddress)); - return; - } - - long recipientMessageTimestamp = messageTransactionsData.get(0).getTimestamp(); - int refundTimeout = LitecoinACCTv2.calcRefundTimeout(recipientMessageTimestamp, lockTimeA); - - // Our calculated refundTimeout should match AT's refundTimeout - if (refundTimeout != crossChainTradeData.refundTimeout) { - LOGGER.debug(() -> String.format("Trade AT refundTimeout '%d' doesn't match our refundTimeout '%d'", crossChainTradeData.refundTimeout, refundTimeout)); - // We'll eventually refund - return; - } - - // We're good to redeem AT - - // Send 'redeem' MESSAGE to AT using both secret - byte[] secretA = tradeBotData.getSecret(); - String qortalReceivingAddress = Base58.encode(tradeBotData.getReceivingAccountInfo()); // Actually contains whole address, not just PKH - byte[] messageData = LitecoinACCTv2.buildRedeemMessage(secretA, qortalReceivingAddress); - String messageRecipient = tradeBotData.getAtAddress(); - - boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); - if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - - messageTransaction.computeNonce(); - messageTransaction.sign(sender); - - // Reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); - return; - } - } - - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE, - () -> String.format("Redeeming AT %s. Funds should arrive at %s", - tradeBotData.getAtAddress(), qortalReceivingAddress)); - } - - /** - * Trade-bot is waiting for Alice to redeem Bob's AT, thus revealing secret-A which is required to spend the LTC funds from P2SH-A. - *

- * It's possible that Bob's AT has reached its trading timeout and automatically refunded QORT back to Bob. In which case, - * trade-bot is done with this specific trade and finalizes in refunded state. - *

- * Assuming trade-bot can extract a valid secret-A from Alice's MESSAGE then trade-bot uses that to redeem the LTC funds from P2SH-A - * to Bob's 'foreign'/Litecoin trade legacy-format address, as derived from trade private key. - *

- * (This could potentially be 'improved' to send LTC to any address of Bob's choosing by changing the transaction output). - *

- * If trade-bot successfully broadcasts the transaction, then this specific trade is done. - * @throws ForeignBlockchainException - */ - private void handleBobWaitingForAtRedeem(Repository repository, TradeBotData tradeBotData, - ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException { - // AT should be 'finished' once Alice has redeemed QORT funds - if (!atData.getIsFinished()) - // Not finished yet - return; - - // If AT is REFUNDED or CANCELLED then something has gone wrong - if (crossChainTradeData.mode == AcctMode.REFUNDED || crossChainTradeData.mode == AcctMode.CANCELLED) { - // Alice hasn't redeemed the QORT, so there is no point in trying to redeem the LTC - TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_REFUNDED, - () -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress())); - - return; - } - - byte[] secretA = LitecoinACCTv2.getInstance().findSecretA(repository, crossChainTradeData); - if (secretA == null) { - LOGGER.debug(() -> String.format("Unable to find secret-A from redeem message to AT %s?", tradeBotData.getAtAddress())); - return; - } - - // Use secret-A to redeem P2SH-A - - Litecoin litecoin = Litecoin.getInstance(); - - byte[] receivingAccountInfo = tradeBotData.getReceivingAccountInfo(); - int lockTimeA = crossChainTradeData.lockTimeA; - byte[] redeemScriptA = BitcoinyHTLC.buildScript(crossChainTradeData.partnerForeignPKH, lockTimeA, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretA); - String p2shAddressA = litecoin.deriveP2shAddress(redeemScriptA); - - // Fee for redeem/refund is subtracted from P2SH-A balance. - long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); - long p2shFee = Litecoin.getInstance().getP2shFee(feeTimestamp); - long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee; - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); - - switch (htlcStatusA) { - case UNFUNDED: - case FUNDING_IN_PROGRESS: - // P2SH-A suddenly not funded? Our best bet at this point is to hope for AT auto-refund - return; - - case REDEEM_IN_PROGRESS: - case REDEEMED: - // Double-check that we have redeemed P2SH-A... - break; - - case REFUND_IN_PROGRESS: - case REFUNDED: - // Wait for AT to auto-refund - return; - - case FUNDED: { - Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount); - ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); - List fundingOutputs = litecoin.getUnspentOutputs(p2shAddressA); - - Transaction p2shRedeemTransaction = BitcoinyHTLC.buildRedeemTransaction(litecoin.getNetworkParameters(), redeemAmount, redeemKey, - fundingOutputs, redeemScriptA, secretA, receivingAccountInfo); - - litecoin.broadcastTransaction(p2shRedeemTransaction); - break; - } - } - - String receivingAddress = litecoin.pkhToAddress(receivingAccountInfo); - - TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_DONE, - () -> String.format("P2SH-A %s redeemed. Funds should arrive at %s", tradeBotData.getAtAddress(), receivingAddress)); - } - - /** - * Trade-bot is attempting to refund P2SH-A. - * @throws ForeignBlockchainException - */ - private void handleAliceRefundingP2shA(Repository repository, TradeBotData tradeBotData, - ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException { - int lockTimeA = tradeBotData.getLockTimeA(); - - // We can't refund P2SH-A until lockTime-A has passed - if (NTP.getTime() <= lockTimeA * 1000L) - return; - - Litecoin litecoin = Litecoin.getInstance(); - - // We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113) - int medianBlockTime = litecoin.getMedianBlockTime(); - if (medianBlockTime <= lockTimeA) - return; - - byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); - String p2shAddressA = litecoin.deriveP2shAddress(redeemScriptA); - - // Fee for redeem/refund is subtracted from P2SH-A balance. - long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); - long p2shFee = Litecoin.getInstance().getP2shFee(feeTimestamp); - long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee; - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); - - switch (htlcStatusA) { - case UNFUNDED: - case FUNDING_IN_PROGRESS: - // Still waiting for P2SH-A to be funded... - return; - - case REDEEM_IN_PROGRESS: - case REDEEMED: - // Too late! - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE, - () -> String.format("P2SH-A %s already spent!", p2shAddressA)); - return; - - case REFUND_IN_PROGRESS: - case REFUNDED: - break; - - case FUNDED:{ - Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount); - ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); - List fundingOutputs = litecoin.getUnspentOutputs(p2shAddressA); - - // Determine receive address for refund - String receiveAddress = litecoin.getUnusedReceiveAddress(tradeBotData.getForeignKey()); - Address receiving = Address.fromString(litecoin.getNetworkParameters(), receiveAddress); - - Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(litecoin.getNetworkParameters(), refundAmount, refundKey, - fundingOutputs, redeemScriptA, lockTimeA, receiving.getHash()); - - litecoin.broadcastTransaction(p2shRefundTransaction); - break; - } - } - - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED, - () -> String.format("LockTime-A reached. Refunded P2SH-A %s. Trade aborted", p2shAddressA)); - } - - /** - * Returns true if Alice finds AT unexpectedly cancelled, refunded, redeemed or locked to someone else. - *

- * Will automatically update trade-bot state to ALICE_REFUNDING_A or ALICE_DONE as necessary. - * - * @throws DataException - * @throws ForeignBlockchainException - */ - private boolean aliceUnexpectedState(Repository repository, TradeBotData tradeBotData, - ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException { - // This is OK - if (!atData.getIsFinished() && crossChainTradeData.mode == AcctMode.OFFERING) - return false; - - boolean isAtLockedToUs = tradeBotData.getTradeNativeAddress().equals(crossChainTradeData.qortalPartnerAddress); - - if (!atData.getIsFinished() && crossChainTradeData.mode == AcctMode.TRADING) - if (isAtLockedToUs) { - // AT is trading with us - OK - return false; - } else { - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A, - () -> String.format("AT %s trading with someone else: %s. Refunding & aborting trade", tradeBotData.getAtAddress(), crossChainTradeData.qortalPartnerAddress)); - - return true; - } - - if (atData.getIsFinished() && crossChainTradeData.mode == AcctMode.REDEEMED && isAtLockedToUs) { - // We've redeemed already? - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE, - () -> String.format("AT %s already redeemed by us. Trade completed", tradeBotData.getAtAddress())); - } else { - // Any other state is not good, so start defensive refund - TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A, - () -> String.format("AT %s cancelled/refunded/redeemed by someone else/invalid state. Refunding & aborting trade", tradeBotData.getAtAddress())); - } - - return true; - } - - private long calcFeeTimestamp(int lockTimeA, int tradeTimeout) { - return (lockTimeA - tradeTimeout * 60) * 1000L; - } - -} diff --git a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java index a31a1a28..a4ae921e 100644 --- a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java @@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -317,20 +318,27 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot { boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + // Do this in a new thread so caller doesn't have to wait for computeNonce() + // In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded + new Thread(() -> { + try (final Repository threadsRepository = RepositoryManager.getRepository()) { + PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); + MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - messageTransaction.computeNonce(); - messageTransaction.sign(sender); + messageTransaction.computeNonce(); + messageTransaction.sign(sender); - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); + // reset repository state to prevent deadlock + threadsRepository.discardChanges(); + ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } catch (DataException e) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); + } + }, "TradeBot response").start(); } TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); diff --git a/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv2TradeBot.java b/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java similarity index 75% rename from src/main/java/org/qortal/controller/tradebot/DogecoinACCTv2TradeBot.java rename to src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java index 96dfd1b1..9834df20 100644 --- a/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv2TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java @@ -1,9 +1,10 @@ package org.qortal.controller.tradebot; +import com.google.common.hash.HashCode; +import com.rust.litewalletjni.LiteWalletJni; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bitcoinj.core.*; -import org.bitcoinj.script.Script.ScriptType; import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PublicKeyAccount; import org.qortal.api.model.crosschain.TradeBotCreateRequest; @@ -45,9 +46,9 @@ import static java.util.stream.Collectors.toMap; *

  • Trade-bot entries
  • * */ -public class DogecoinACCTv2TradeBot implements AcctTradeBot { +public class PirateChainACCTv3TradeBot implements AcctTradeBot { - private static final Logger LOGGER = LogManager.getLogger(DogecoinACCTv2TradeBot.class); + private static final Logger LOGGER = LogManager.getLogger(PirateChainACCTv3TradeBot.class); public enum State implements TradeBot.StateNameAndValueSupplier { BOB_WAITING_FOR_AT_CONFIRM(10, false, false), @@ -91,18 +92,18 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { /** Maximum time Bob waits for his AT creation transaction to be confirmed into a block. (milliseconds) */ private static final long MAX_AT_CONFIRMATION_PERIOD = 24 * 60 * 60 * 1000L; // ms - private static DogecoinACCTv2TradeBot instance; + private static PirateChainACCTv3TradeBot instance; private final List endStates = Arrays.asList(State.BOB_DONE, State.BOB_REFUNDED, State.ALICE_DONE, State.ALICE_REFUNDING_A, State.ALICE_REFUNDED).stream() .map(State::name) .collect(Collectors.toUnmodifiableList()); - private DogecoinACCTv2TradeBot() { + private PirateChainACCTv3TradeBot() { } - public static synchronized DogecoinACCTv2TradeBot getInstance() { + public static synchronized PirateChainACCTv3TradeBot getInstance() { if (instance == null) - instance = new DogecoinACCTv2TradeBot(); + instance = new PirateChainACCTv3TradeBot(); return instance; } @@ -113,7 +114,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { } /** - * Creates a new trade-bot entry from the "Bob" viewpoint, i.e. OFFERing QORT in exchange for DOGE. + * Creates a new trade-bot entry from the "Bob" viewpoint, i.e. OFFERing QORT in exchange for ARRR. *

    * Generates: *

      @@ -122,14 +123,14 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { * Derives: *
        *
      • 'native' (as in Qortal) public key, public key hash, address (starting with Q)
      • - *
      • 'foreign' (as in Dogecoin) public key, public key hash
      • + *
      • 'foreign' (as in PirateChain) public key, public key hash
      • *
      * A Qortal AT is then constructed including the following as constants in the 'data segment': *
        *
      • 'native'/Qortal 'trade' address - used as a MESSAGE contact
      • - *
      • 'foreign'/Dogecoin public key hash - used by Alice's P2SH scripts to allow redeem
      • + *
      • 'foreign'/PirateChain public key hash - used by Alice's P2SH scripts to allow redeem
      • *
      • QORT amount on offer by Bob
      • - *
      • DOGE amount expected in return by Bob (from Alice)
      • + *
      • ARRR amount expected in return by Bob (from Alice)
      • *
      • trading timeout, in case things go wrong and everyone needs to refund
      • *
      * Returns a DEPLOY_AT transaction that needs to be signed and broadcast to the Qortal network. @@ -151,17 +152,18 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); - // Convert Dogecoin receiving address into public key hash (we only support P2PKH at this time) - Address dogecoinReceivingAddress; - try { - dogecoinReceivingAddress = Address.fromString(Dogecoin.getInstance().getNetworkParameters(), tradeBotCreateRequest.receivingAddress); - } catch (AddressFormatException e) { - throw new DataException("Unsupported Dogecoin receiving address: " + tradeBotCreateRequest.receivingAddress); + // ARRR wallet must be loaded before a trade can be created + // This is to stop trades from nodes on unsupported architectures (e.g. 32bit) + if (!LiteWalletJni.isLoaded()) { + throw new DataException("Pirate wallet not found. Check wallets screen for details."); } - if (dogecoinReceivingAddress.getOutputScriptType() != ScriptType.P2PKH) - throw new DataException("Unsupported Dogecoin receiving address: " + tradeBotCreateRequest.receivingAddress); - byte[] dogecoinReceivingAccountInfo = dogecoinReceivingAddress.getHash(); + if (!PirateChain.getInstance().isValidAddress(tradeBotCreateRequest.receivingAddress)) { + throw new DataException("Unsupported Pirate Chain receiving address: " + tradeBotCreateRequest.receivingAddress); + } + + Bech32.Bech32Data decodedReceivingAddress = Bech32.decode(tradeBotCreateRequest.receivingAddress); + byte[] pirateChainReceivingAccountInfo = decodedReceivingAddress.data; PublicKeyAccount creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey); @@ -172,11 +174,11 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { byte[] signature = null; BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, creator.getPublicKey(), fee, signature); - String name = "QORT/DOGE ACCT"; - String description = "QORT/DOGE cross-chain trade"; + String name = "QORT/ARRR ACCT"; + String description = "QORT/ARRR cross-chain trade"; String aTType = "ACCT"; - String tags = "ACCT QORT DOGE"; - byte[] creationBytes = DogecoinACCTv2.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, tradeBotCreateRequest.qortAmount, + String tags = "ACCT QORT ARRR"; + byte[] creationBytes = PirateChainACCTv3.buildQortalAT(tradeNativeAddress, tradeForeignPublicKey, tradeBotCreateRequest.qortAmount, tradeBotCreateRequest.foreignAmount, tradeBotCreateRequest.tradeTimeout); long amount = tradeBotCreateRequest.fundingQortAmount; @@ -189,14 +191,14 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { DeployAtTransaction.ensureATAddress(deployAtTransactionData); String atAddress = deployAtTransactionData.getAtAddress(); - TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, DogecoinACCTv2.NAME, + TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, PirateChainACCTv3.NAME, State.BOB_WAITING_FOR_AT_CONFIRM.name(), State.BOB_WAITING_FOR_AT_CONFIRM.value, creator.getAddress(), atAddress, timestamp, tradeBotCreateRequest.qortAmount, tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, null, null, - SupportedBlockchain.DOGECOIN.name(), + SupportedBlockchain.PIRATECHAIN.name(), tradeForeignPublicKey, tradeForeignPublicKeyHash, - tradeBotCreateRequest.foreignAmount, null, null, null, dogecoinReceivingAccountInfo); + tradeBotCreateRequest.foreignAmount, null, null, null, pirateChainReceivingAccountInfo); TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Built AT %s. Waiting for deployment", atAddress)); @@ -212,15 +214,15 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { } /** - * Creates a trade-bot entry from the 'Alice' viewpoint, i.e. matching DOGE to an existing offer. + * Creates a trade-bot entry from the 'Alice' viewpoint, i.e. matching ARRR to an existing offer. *

      * Requires a chosen trade offer from Bob, passed by crossChainTradeData - * and access to a Dogecoin wallet via xprv58. + * and access to a PirateChain wallet via xprv58. *

      * The crossChainTradeData contains the current trade offer state * as extracted from the AT's data segment. *

      - * Access to a funded wallet is via a Dogecoin BIP32 hierarchical deterministic key, + * Access to a funded wallet is via a PirateChain BIP32 hierarchical deterministic key, * passed via xprv58. * This key will be stored in your node's database * to allow trade-bot to create/fund the necessary P2SH transactions! @@ -230,26 +232,26 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { * As an example, the xprv58 can be extract from a legacy, password-less * Electrum wallet by going to the console tab and entering:
      * wallet.keystore.xprv
      - * which should result in a base58 string starting with either 'xprv' (for Dogecoin main-net) - * or 'tprv' for (Dogecoin test-net). + * which should result in a base58 string starting with either 'xprv' (for PirateChain main-net) + * or 'tprv' for (PirateChain test-net). *

      * It is envisaged that the value in xprv58 will actually come from a Qortal-UI-managed wallet. *

      * If sufficient funds are available, this method will actually fund the P2SH-A - * with the Dogecoin amount expected by 'Bob'. + * with the PirateChain amount expected by 'Bob'. *

      - * If the Dogecoin transaction is successfully broadcast to the network then + * If the PirateChain transaction is successfully broadcast to the network then * we also send a MESSAGE to Bob's trade-bot to let them know. *

      * The trade-bot entry is saved to the repository and the cross-chain trading process commences. *

      * @param repository * @param crossChainTradeData chosen trade OFFER that Alice wants to match - * @param xprv58 funded wallet xprv in base58 - * @return true if P2SH-A funding transaction successfully broadcast to Dogecoin network, false otherwise + * @param seed58 funded wallet xprv in base58 + * @return true if P2SH-A funding transaction successfully broadcast to PirateChain network, false otherwise * @throws DataException */ - public ResponseResult startResponse(Repository repository, ATData atData, ACCT acct, CrossChainTradeData crossChainTradeData, String xprv58, String receivingAddress) throws DataException { + public ResponseResult startResponse(Repository repository, ATData atData, ACCT acct, CrossChainTradeData crossChainTradeData, String seed58, String receivingAddress) throws DataException { byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); byte[] secretA = TradeBot.generateSecret(); byte[] hashOfSecretA = Crypto.hash160(secretA); @@ -262,18 +264,22 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); byte[] receivingPublicKeyHash = Base58.decode(receivingAddress); // Actually the whole address, not just PKH + String tradePrivateKey58 = Base58.encode(tradePrivateKey); + String tradeForeignPublicKey58 = Base58.encode(tradeForeignPublicKey); + String secret58 = Base58.encode(secretA); + // We need to generate lockTime-A: add tradeTimeout to now long now = NTP.getTime(); int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (now / 1000L); - TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, DogecoinACCTv2.NAME, + TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, PirateChainACCTv3.NAME, State.ALICE_WAITING_FOR_AT_LOCK.name(), State.ALICE_WAITING_FOR_AT_LOCK.value, receivingAddress, crossChainTradeData.qortalAtAddress, now, crossChainTradeData.qortAmount, tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, secretA, hashOfSecretA, - SupportedBlockchain.DOGECOIN.name(), + SupportedBlockchain.PIRATECHAIN.name(), tradeForeignPublicKey, tradeForeignPublicKeyHash, - crossChainTradeData.expectedForeignAmount, xprv58, null, lockTimeA, receivingPublicKeyHash); + crossChainTradeData.expectedForeignAmount, seed58, null, lockTimeA, receivingPublicKeyHash); // Attempt to backup the trade bot data // Include tradeBotData as an additional parameter, since it's not in the repository yet @@ -282,9 +288,9 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { // Check we have enough funds via xprv58 to fund P2SH to cover expectedForeignAmount long p2shFee; try { - p2shFee = Dogecoin.getInstance().getP2shFee(now); + p2shFee = PirateChain.getInstance().getP2shFee(now); } catch (ForeignBlockchainException e) { - LOGGER.debug("Couldn't estimate Dogecoin fees?"); + LOGGER.debug("Couldn't estimate PirateChain fees?"); return ResponseResult.NETWORK_ISSUE; } @@ -293,26 +299,23 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { long amountA = crossChainTradeData.expectedForeignAmount + p2shFee /*redeeming/refunding P2SH-A*/; // P2SH-A to be funded - byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(tradeForeignPublicKeyHash, lockTimeA, crossChainTradeData.creatorForeignPKH, hashOfSecretA); - String p2shAddress = Dogecoin.getInstance().deriveP2shAddress(redeemScriptBytes); + byte[] redeemScriptBytes = PirateChainHTLC.buildScript(tradeForeignPublicKey, lockTimeA, crossChainTradeData.creatorForeignPKH, hashOfSecretA); + String p2shAddressT3 = PirateChain.getInstance().deriveP2shAddress(redeemScriptBytes); // Use t3 prefix when funding + byte[] redeemScriptWithPrefixBytes = PirateChainHTLC.buildScriptWithPrefix(tradeForeignPublicKey, lockTimeA, crossChainTradeData.creatorForeignPKH, hashOfSecretA); + String redeemScriptWithPrefix58 = Base58.encode(redeemScriptWithPrefixBytes); - // Build transaction for funding P2SH-A - Transaction p2shFundingTransaction = Dogecoin.getInstance().buildSpend(tradeBotData.getForeignKey(), p2shAddress, amountA); - if (p2shFundingTransaction == null) { - LOGGER.debug("Unable to build P2SH-A funding transaction - lack of funds?"); + // Send to P2SH address + try { + String txid = PirateChain.getInstance().fundP2SH(seed58, p2shAddressT3, amountA, redeemScriptWithPrefix58); + LOGGER.info("fundingTxidHex: {}", txid); + + } catch (ForeignBlockchainException e) { + LOGGER.debug("Unable to build and send P2SH-A funding transaction - lack of funds?"); return ResponseResult.BALANCE_ISSUE; } - try { - Dogecoin.getInstance().broadcastTransaction(p2shFundingTransaction); - } catch (ForeignBlockchainException e) { - // We couldn't fund P2SH-A at this time - LOGGER.debug("Couldn't broadcast P2SH-A funding transaction?"); - return ResponseResult.NETWORK_ISSUE; - } - // Attempt to send MESSAGE to Bob's Qortal trade address - byte[] messageData = DogecoinACCTv2.buildOfferMessage(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getHashOfSecret(), tradeBotData.getLockTimeA()); + byte[] messageData = PirateChainACCTv3.buildOfferMessage(tradeBotData.getTradeForeignPublicKey(), tradeBotData.getHashOfSecret(), tradeBotData.getLockTimeA()); String messageRecipient = crossChainTradeData.qortalCreatorTradeAddress; boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); @@ -333,11 +336,21 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { } } - TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); + TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddressT3)); return ResponseResult.OK; } + public static String hex(byte[] bytes) { + StringBuilder result = new StringBuilder(); + for (byte aByte : bytes) { + result.append(String.format("%02x", aByte)); + // upper case + // result.append(String.format("%02X", aByte)); + } + return result.toString(); + } + @Override public boolean canDelete(Repository repository, TradeBotData tradeBotData) throws DataException { State tradeBotState = State.valueOf(tradeBotData.getStateValue()); @@ -354,6 +367,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { case BOB_DONE: case ALICE_REFUNDED: case BOB_REFUNDED: + case ALICE_REFUNDING_A: return true; default: @@ -381,7 +395,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { } if (tradeBotState.requiresTradeData) { - tradeData = DogecoinACCTv2.getInstance().populateTradeData(repository, atData); + tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); if (tradeData == null) { LOGGER.warn(() -> String.format("Unable to fetch ACCT trade data for AT %s from repository", tradeBotData.getAtAddress())); return; @@ -462,7 +476,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { *

      * Details from Alice are used to derive P2SH-A address and this is checked for funding balance. *

      - * Assuming P2SH-A has at least expected Dogecoin balance, + * Assuming P2SH-A has at least expected PirateChain balance, * Bob's trade-bot constructs a zero-fee, PoW MESSAGE to send to Bob's AT with more trade details. *

      * On processing this MESSAGE, Bob's AT should switch into 'TRADE' mode and only trade with Alice. @@ -480,7 +494,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { return; } - Dogecoin dogecoin = Dogecoin.getInstance(); + PirateChain pirateChain = PirateChain.getInstance(); String address = tradeBotData.getTradeNativeAddress(); List messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, address, null, null, null); @@ -489,27 +503,27 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { if (messageTransactionData.isText()) continue; - // We're expecting: HASH160(secret-A), Alice's Dogecoin pubkeyhash and lockTime-A + // We're expecting: HASH160(secret-A), Alice's PirateChain pubkeyhash and lockTime-A byte[] messageData = messageTransactionData.getData(); - DogecoinACCTv2.OfferMessageData offerMessageData = DogecoinACCTv2.extractOfferMessageData(messageData); + PirateChainACCTv3.OfferMessageData offerMessageData = PirateChainACCTv3.extractOfferMessageData(messageData); if (offerMessageData == null) continue; - byte[] aliceForeignPublicKeyHash = offerMessageData.partnerDogecoinPKH; + byte[] aliceForeignPublicKey = offerMessageData.partnerPirateChainPublicKey; byte[] hashOfSecretA = offerMessageData.hashOfSecretA; int lockTimeA = (int) offerMessageData.lockTimeA; long messageTimestamp = messageTransactionData.getTimestamp(); - int refundTimeout = DogecoinACCTv2.calcRefundTimeout(messageTimestamp, lockTimeA); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(messageTimestamp, lockTimeA); // Determine P2SH-A address and confirm funded - byte[] redeemScriptA = BitcoinyHTLC.buildScript(aliceForeignPublicKeyHash, lockTimeA, tradeBotData.getTradeForeignPublicKeyHash(), hashOfSecretA); - String p2shAddressA = dogecoin.deriveP2shAddress(redeemScriptA); + byte[] redeemScriptA = PirateChainHTLC.buildScript(aliceForeignPublicKey, lockTimeA, tradeBotData.getTradeForeignPublicKey(), hashOfSecretA); + String p2shAddress = pirateChain.deriveP2shAddressBPrefix(redeemScriptA); // Use 'b' prefix when checking status long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); - long p2shFee = Dogecoin.getInstance().getP2shFee(feeTimestamp); + long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp); final long minimumAmountA = tradeBotData.getForeignAmount() + p2shFee; - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(dogecoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); + BitcoinyHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA); switch (htlcStatusA) { case UNFUNDED: @@ -521,7 +535,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { case REDEEMED: // We've already redeemed this? TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_DONE, - () -> String.format("P2SH-A %s already spent? Assuming trade complete", p2shAddressA)); + () -> String.format("P2SH-A %s already spent? Assuming trade complete", p2shAddress)); return; case REFUND_IN_PROGRESS: @@ -539,7 +553,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { String aliceNativeAddress = Crypto.toAddress(messageTransactionData.getCreatorPublicKey()); // Build outgoing message, padding each part to 32 bytes to make it easier for AT to consume - byte[] outgoingMessageData = DogecoinACCTv2.buildTradeMessage(aliceNativeAddress, aliceForeignPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); + byte[] outgoingMessageData = PirateChainACCTv3.buildTradeMessage(aliceNativeAddress, aliceForeignPublicKey, hashOfSecretA, lockTimeA, refundTimeout); String messageRecipient = tradeBotData.getAtAddress(); boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, outgoingMessageData); @@ -578,7 +592,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { *

      * If all is well, trade-bot then redeems AT using Alice's secret-A, releasing Bob's QORT to Alice. *

      - * In revealing a valid secret-A, Bob can then redeem the DOGE funds from P2SH-A. + * In revealing a valid secret-A, Bob can then redeem the ARRR funds from P2SH-A. *

      * @throws ForeignBlockchainException */ @@ -587,19 +601,19 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { if (aliceUnexpectedState(repository, tradeBotData, atData, crossChainTradeData)) return; - Dogecoin dogecoin = Dogecoin.getInstance(); + PirateChain pirateChain = PirateChain.getInstance(); int lockTimeA = tradeBotData.getLockTimeA(); // Refund P2SH-A if we've passed lockTime-A if (NTP.getTime() >= lockTimeA * 1000L) { - byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); - String p2shAddressA = dogecoin.deriveP2shAddress(redeemScriptA); + byte[] redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); + String p2shAddress = pirateChain.deriveP2shAddressBPrefix(redeemScriptA); // Use 'b' prefix when checking status long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); - long p2shFee = Dogecoin.getInstance().getP2shFee(feeTimestamp); + long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp); long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee; - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(dogecoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); + BitcoinyHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA); switch (htlcStatusA) { case UNFUNDED: @@ -611,21 +625,21 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { case REDEEMED: // Already redeemed? TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE, - () -> String.format("P2SH-A %s already spent? Assuming trade completed", p2shAddressA)); + () -> String.format("P2SH-A %s already spent? Assuming trade completed", p2shAddress)); return; case REFUND_IN_PROGRESS: case REFUNDED: TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED, - () -> String.format("P2SH-A %s already refunded. Trade aborted", p2shAddressA)); + () -> String.format("P2SH-A %s already refunded. Trade aborted", p2shAddress)); return; } TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A, () -> atData.getIsFinished() - ? String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddressA) - : String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddressA)); + ? String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddress) + : String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddress)); return; } @@ -645,7 +659,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { } long recipientMessageTimestamp = messageTransactionsData.get(0).getTimestamp(); - int refundTimeout = DogecoinACCTv2.calcRefundTimeout(recipientMessageTimestamp, lockTimeA); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(recipientMessageTimestamp, lockTimeA); // Our calculated refundTimeout should match AT's refundTimeout if (refundTimeout != crossChainTradeData.refundTimeout) { @@ -659,7 +673,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { // Send 'redeem' MESSAGE to AT using both secret byte[] secretA = tradeBotData.getSecret(); String qortalReceivingAddress = Base58.encode(tradeBotData.getReceivingAccountInfo()); // Actually contains whole address, not just PKH - byte[] messageData = DogecoinACCTv2.buildRedeemMessage(secretA, qortalReceivingAddress); + byte[] messageData = PirateChainACCTv3.buildRedeemMessage(secretA, qortalReceivingAddress); String messageRecipient = tradeBotData.getAtAddress(); boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); @@ -686,15 +700,15 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { } /** - * Trade-bot is waiting for Alice to redeem Bob's AT, thus revealing secret-A which is required to spend the DOGE funds from P2SH-A. + * Trade-bot is waiting for Alice to redeem Bob's AT, thus revealing secret-A which is required to spend the ARRR funds from P2SH-A. *

      * It's possible that Bob's AT has reached its trading timeout and automatically refunded QORT back to Bob. In which case, * trade-bot is done with this specific trade and finalizes in refunded state. *

      - * Assuming trade-bot can extract a valid secret-A from Alice's MESSAGE then trade-bot uses that to redeem the DOGE funds from P2SH-A - * to Bob's 'foreign'/Dogecoin trade legacy-format address, as derived from trade private key. + * Assuming trade-bot can extract a valid secret-A from Alice's MESSAGE then trade-bot uses that to redeem the ARRR funds from P2SH-A + * to Bob's 'foreign'/PirateChain trade legacy-format address, as derived from trade private key. *

      - * (This could potentially be 'improved' to send DOGE to any address of Bob's choosing by changing the transaction output). + * (This could potentially be 'improved' to send ARRR to any address of Bob's choosing by changing the transaction output). *

      * If trade-bot successfully broadcasts the transaction, then this specific trade is done. * @throws ForeignBlockchainException @@ -708,14 +722,14 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { // If AT is REFUNDED or CANCELLED then something has gone wrong if (crossChainTradeData.mode == AcctMode.REFUNDED || crossChainTradeData.mode == AcctMode.CANCELLED) { - // Alice hasn't redeemed the QORT, so there is no point in trying to redeem the DOGE + // Alice hasn't redeemed the QORT, so there is no point in trying to redeem the ARRR TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_REFUNDED, () -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress())); return; } - byte[] secretA = DogecoinACCTv2.getInstance().findSecretA(repository, crossChainTradeData); + byte[] secretA = PirateChainACCTv3.getInstance().findSecretA(repository, crossChainTradeData); if (secretA == null) { LOGGER.debug(() -> String.format("Unable to find secret-A from redeem message to AT %s?", tradeBotData.getAtAddress())); return; @@ -723,18 +737,21 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { // Use secret-A to redeem P2SH-A - Dogecoin dogecoin = Dogecoin.getInstance(); + PirateChain pirateChain = PirateChain.getInstance(); byte[] receivingAccountInfo = tradeBotData.getReceivingAccountInfo(); int lockTimeA = crossChainTradeData.lockTimeA; - byte[] redeemScriptA = BitcoinyHTLC.buildScript(crossChainTradeData.partnerForeignPKH, lockTimeA, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretA); - String p2shAddressA = dogecoin.deriveP2shAddress(redeemScriptA); + byte[] redeemScriptA = PirateChainHTLC.buildScript(crossChainTradeData.partnerForeignPKH, lockTimeA, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretA); + String p2shAddress = pirateChain.deriveP2shAddressBPrefix(redeemScriptA); // Use 'b' prefix when checking status + String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA); // Use 't3' prefix when refunding // Fee for redeem/refund is subtracted from P2SH-A balance. long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); - long p2shFee = Dogecoin.getInstance().getP2shFee(feeTimestamp); + long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp); long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee; - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(dogecoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); + String receivingAddress = Bech32.encode("zs", receivingAccountInfo); + + BitcoinyHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA); switch (htlcStatusA) { case UNFUNDED: @@ -753,20 +770,27 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { return; case FUNDED: { + // Get funding txid + String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA); + if (fundingTxidHex == null) { + throw new ForeignBlockchainException("Missing funding txid when redeeming P2SH"); + } + String fundingTxid58 = Base58.encode(HashCode.fromString(fundingTxidHex).asBytes()); + + // Redeem P2SH Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount); - ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); - List fundingOutputs = dogecoin.getUnspentOutputs(p2shAddressA); + byte[] privateKey = tradeBotData.getTradePrivateKey(); + String secret58 = Base58.encode(secretA); + String privateKey58 = Base58.encode(privateKey); + String redeemScript58 = Base58.encode(redeemScriptA); - Transaction p2shRedeemTransaction = BitcoinyHTLC.buildRedeemTransaction(dogecoin.getNetworkParameters(), redeemAmount, redeemKey, - fundingOutputs, redeemScriptA, secretA, receivingAccountInfo); - - dogecoin.broadcastTransaction(p2shRedeemTransaction); + String txid = PirateChain.getInstance().redeemP2sh(p2shAddressT3, receivingAddress, redeemAmount.value, + redeemScript58, fundingTxid58, secret58, privateKey58); + LOGGER.info("Redeem txid: {}", txid); break; } } - String receivingAddress = dogecoin.pkhToAddress(receivingAccountInfo); - TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_DONE, () -> String.format("P2SH-A %s redeemed. Funds should arrive at %s", tradeBotData.getAtAddress(), receivingAddress)); } @@ -783,21 +807,22 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { if (NTP.getTime() <= lockTimeA * 1000L) return; - Dogecoin dogecoin = Dogecoin.getInstance(); + PirateChain pirateChain = PirateChain.getInstance(); // We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113) - int medianBlockTime = dogecoin.getMedianBlockTime(); + int medianBlockTime = pirateChain.getMedianBlockTime(); if (medianBlockTime <= lockTimeA) return; - byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); - String p2shAddressA = dogecoin.deriveP2shAddress(redeemScriptA); + byte[] redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret()); + String p2shAddress = pirateChain.deriveP2shAddressBPrefix(redeemScriptA); // Use 'b' prefix when checking status + String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA); // Use 't3' prefix when refunding // Fee for redeem/refund is subtracted from P2SH-A balance. long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout); - long p2shFee = Dogecoin.getInstance().getP2shFee(feeTimestamp); + long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp); long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee; - BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(dogecoin.getBlockchainProvider(), p2shAddressA, minimumAmountA); + BitcoinyHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA); switch (htlcStatusA) { case UNFUNDED: @@ -809,7 +834,7 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { case REDEEMED: // Too late! TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE, - () -> String.format("P2SH-A %s already spent!", p2shAddressA)); + () -> String.format("P2SH-A %s already spent!", p2shAddress)); return; case REFUND_IN_PROGRESS: @@ -817,24 +842,28 @@ public class DogecoinACCTv2TradeBot implements AcctTradeBot { break; case FUNDED:{ + // Get funding txid + String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA); + if (fundingTxidHex == null) { + throw new ForeignBlockchainException("Missing funding txid when refunding P2SH"); + } + String fundingTxid58 = Base58.encode(HashCode.fromString(fundingTxidHex).asBytes()); + Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount); - ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); - List fundingOutputs = dogecoin.getUnspentOutputs(p2shAddressA); + byte[] privateKey = tradeBotData.getTradePrivateKey(); + String privateKey58 = Base58.encode(privateKey); + String redeemScript58 = Base58.encode(redeemScriptA); + String receivingAddress = pirateChain.getWalletAddress(tradeBotData.getForeignKey()); - // Determine receive address for refund - String receiveAddress = dogecoin.getUnusedReceiveAddress(tradeBotData.getForeignKey()); - Address receiving = Address.fromString(dogecoin.getNetworkParameters(), receiveAddress); - - Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(dogecoin.getNetworkParameters(), refundAmount, refundKey, - fundingOutputs, redeemScriptA, lockTimeA, receiving.getHash()); - - dogecoin.broadcastTransaction(p2shRefundTransaction); + String txid = PirateChain.getInstance().refundP2sh(p2shAddressT3, + receivingAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTimeA, privateKey58); + LOGGER.info("Refund txid: {}", txid); break; } } TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED, - () -> String.format("LockTime-A reached. Refunded P2SH-A %s. Trade aborted", p2shAddressA)); + () -> String.format("LockTime-A reached. Refunded P2SH-A %s. Trade aborted", p2shAddress)); } /** diff --git a/src/main/java/org/qortal/controller/tradebot/TradeBot.java b/src/main/java/org/qortal/controller/tradebot/TradeBot.java index 0583a0f0..5880f561 100644 --- a/src/main/java/org/qortal/controller/tradebot/TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/TradeBot.java @@ -96,13 +96,12 @@ public class TradeBot implements Listener { acctTradeBotSuppliers.put(BitcoinACCTv1.class, BitcoinACCTv1TradeBot::getInstance); acctTradeBotSuppliers.put(BitcoinACCTv3.class, BitcoinACCTv3TradeBot::getInstance); acctTradeBotSuppliers.put(LitecoinACCTv1.class, LitecoinACCTv1TradeBot::getInstance); - acctTradeBotSuppliers.put(LitecoinACCTv2.class, LitecoinACCTv2TradeBot::getInstance); acctTradeBotSuppliers.put(LitecoinACCTv3.class, LitecoinACCTv3TradeBot::getInstance); acctTradeBotSuppliers.put(DogecoinACCTv1.class, DogecoinACCTv1TradeBot::getInstance); - acctTradeBotSuppliers.put(DogecoinACCTv2.class, DogecoinACCTv2TradeBot::getInstance); acctTradeBotSuppliers.put(DogecoinACCTv3.class, DogecoinACCTv3TradeBot::getInstance); acctTradeBotSuppliers.put(DigibyteACCTv3.class, DigibyteACCTv3TradeBot::getInstance); acctTradeBotSuppliers.put(RavencoinACCTv3.class, RavencoinACCTv3TradeBot::getInstance); + acctTradeBotSuppliers.put(PirateChainACCTv3.class, PirateChainACCTv3TradeBot::getInstance); } private static TradeBot instance; @@ -292,14 +291,14 @@ public class TradeBot implements Listener { } public static byte[] deriveTradeNativePublicKey(byte[] privateKey) { - return PrivateKeyAccount.toPublicKey(privateKey); + return Crypto.toPublicKey(privateKey); } public static byte[] deriveTradeForeignPublicKey(byte[] privateKey) { return ECKey.fromPrivate(privateKey).getPubKey(); } - /*package*/ static byte[] generateSecret() { + /*package*/ public static byte[] generateSecret() { byte[] secret = new byte[32]; RANDOM.nextBytes(secret); return secret; @@ -469,9 +468,6 @@ public class TradeBot implements Listener { List safeTradePresences = List.copyOf(this.safeAllTradePresencesByPubkey.values()); - if (safeTradePresences.isEmpty()) - return; - LOGGER.debug("Broadcasting all {} known trade presences. Next broadcast timestamp: {}", safeTradePresences.size(), nextTradePresenceBroadcastTimestamp ); @@ -638,7 +634,7 @@ public class TradeBot implements Listener { } if (newCount > 0) { - LOGGER.debug("New trade presences: {}", newCount); + LOGGER.debug("New trade presences: {}, all trade presences: {}", newCount, allTradePresencesByPubkey.size()); rebuildSafeAllTradePresences(); } } diff --git a/src/main/java/org/qortal/crosschain/Bitcoin.java b/src/main/java/org/qortal/crosschain/Bitcoin.java index afd42590..7fec5a17 100644 --- a/src/main/java/org/qortal/crosschain/Bitcoin.java +++ b/src/main/java/org/qortal/crosschain/Bitcoin.java @@ -174,6 +174,8 @@ public class Bitcoin extends Bitcoiny { Context bitcoinjContext = new Context(bitcoinNet.getParams()); instance = new Bitcoin(bitcoinNet, electrumX, bitcoinjContext, CURRENCY_CODE); + + electrumX.setBlockchain(instance); } return instance; diff --git a/src/main/java/org/qortal/crosschain/Bitcoiny.java b/src/main/java/org/qortal/crosschain/Bitcoiny.java index f66ea939..c08bd91e 100644 --- a/src/main/java/org/qortal/crosschain/Bitcoiny.java +++ b/src/main/java/org/qortal/crosschain/Bitcoiny.java @@ -29,6 +29,7 @@ import org.bitcoinj.wallet.SendRequest; import org.bitcoinj.wallet.Wallet; import org.qortal.api.model.SimpleForeignTransaction; import org.qortal.crypto.Crypto; +import org.qortal.settings.Settings; import org.qortal.utils.Amounts; import org.qortal.utils.BitTwiddling; @@ -42,7 +43,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { public static final int HASH160_LENGTH = 20; - protected final BitcoinyBlockchainProvider blockchain; + protected final BitcoinyBlockchainProvider blockchainProvider; protected final Context bitcoinjContext; protected final String currencyCode; @@ -61,18 +62,13 @@ public abstract class Bitcoiny implements ForeignBlockchain { /** How many wallet keys to generate in each batch. */ private static final int WALLET_KEY_LOOKAHEAD_INCREMENT = 3; - /** How many wallet keys to generate when using bitcoinj as the data provider. - * We must use a higher value here since we are unable to request multiple batches of keys. - * Without this, the bitcoinj state can be missing transactions, causing errors such as "insufficient balance". */ - private static final int WALLET_KEY_LOOKAHEAD_INCREMENT_BITCOINJ = 50; - /** Byte offset into raw block headers to block timestamp. */ private static final int TIMESTAMP_OFFSET = 4 + 32 + 32; // Constructors and instance - protected Bitcoiny(BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) { - this.blockchain = blockchain; + protected Bitcoiny(BitcoinyBlockchainProvider blockchainProvider, Context bitcoinjContext, String currencyCode) { + this.blockchainProvider = blockchainProvider; this.bitcoinjContext = bitcoinjContext; this.currencyCode = currencyCode; @@ -82,7 +78,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { // Getters & setters public BitcoinyBlockchainProvider getBlockchainProvider() { - return this.blockchain; + return this.blockchainProvider; } public Context getBitcoinjContext() { @@ -155,10 +151,10 @@ public abstract class Bitcoiny implements ForeignBlockchain { * @throws ForeignBlockchainException if error occurs */ public int getMedianBlockTime() throws ForeignBlockchainException { - int height = this.blockchain.getCurrentHeight(); + int height = this.blockchainProvider.getCurrentHeight(); // Grab latest 11 blocks - List blockHeaders = this.blockchain.getRawBlockHeaders(height - 11, 11); + List blockHeaders = this.blockchainProvider.getRawBlockHeaders(height - 11, 11); if (blockHeaders.size() < 11) throw new ForeignBlockchainException("Not enough blocks to determine median block time"); @@ -197,7 +193,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { * @throws ForeignBlockchainException if there was an error */ public long getConfirmedBalance(String base58Address) throws ForeignBlockchainException { - return this.blockchain.getConfirmedBalance(addressToScriptPubKey(base58Address)); + return this.blockchainProvider.getConfirmedBalance(addressToScriptPubKey(base58Address)); } /** @@ -208,7 +204,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { */ // TODO: don't return bitcoinj-based objects like TransactionOutput, use BitcoinyTransaction.Output instead public List getUnspentOutputs(String base58Address) throws ForeignBlockchainException { - List unspentOutputs = this.blockchain.getUnspentOutputs(addressToScriptPubKey(base58Address), false); + List unspentOutputs = this.blockchainProvider.getUnspentOutputs(addressToScriptPubKey(base58Address), false); List unspentTransactionOutputs = new ArrayList<>(); for (UnspentOutput unspentOutput : unspentOutputs) { @@ -228,7 +224,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { */ // TODO: don't return bitcoinj-based objects like TransactionOutput, use BitcoinyTransaction.Output instead public List getOutputs(byte[] txHash) throws ForeignBlockchainException { - byte[] rawTransactionBytes = this.blockchain.getRawTransaction(txHash); + byte[] rawTransactionBytes = this.blockchainProvider.getRawTransaction(txHash); Context.propagate(bitcoinjContext); Transaction transaction = new Transaction(this.params, rawTransactionBytes); @@ -245,7 +241,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { ForeignBlockchainException e2 = null; while (retries <= 3) { try { - return this.blockchain.getAddressTransactions(scriptPubKey, includeUnconfirmed); + return this.blockchainProvider.getAddressTransactions(scriptPubKey, includeUnconfirmed); } catch (ForeignBlockchainException e) { e2 = e; retries++; @@ -261,7 +257,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { * @throws ForeignBlockchainException if there was an error. */ public List getAddressTransactions(String base58Address, boolean includeUnconfirmed) throws ForeignBlockchainException { - return this.blockchain.getAddressTransactions(addressToScriptPubKey(base58Address), includeUnconfirmed); + return this.blockchainProvider.getAddressTransactions(addressToScriptPubKey(base58Address), includeUnconfirmed); } /** @@ -270,11 +266,11 @@ public abstract class Bitcoiny implements ForeignBlockchain { * @throws ForeignBlockchainException if there was an error */ public List getAddressTransactions(String base58Address) throws ForeignBlockchainException { - List transactionHashes = this.blockchain.getAddressTransactions(addressToScriptPubKey(base58Address), false); + List transactionHashes = this.blockchainProvider.getAddressTransactions(addressToScriptPubKey(base58Address), false); List rawTransactions = new ArrayList<>(); for (TransactionHash transactionInfo : transactionHashes) { - byte[] rawTransaction = this.blockchain.getRawTransaction(HashCode.fromString(transactionInfo.txHash).asBytes()); + byte[] rawTransaction = this.blockchainProvider.getRawTransaction(HashCode.fromString(transactionInfo.txHash).asBytes()); rawTransactions.add(rawTransaction); } @@ -292,7 +288,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { ForeignBlockchainException e2 = null; while (retries <= 3) { try { - return this.blockchain.getTransaction(txHash); + return this.blockchainProvider.getTransaction(txHash); } catch (ForeignBlockchainException e) { e2 = e; retries++; @@ -307,7 +303,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { * @throws ForeignBlockchainException if error occurs */ public void broadcastTransaction(Transaction transaction) throws ForeignBlockchainException { - this.blockchain.broadcastTransaction(transaction.bitcoinSerialize()); + this.blockchainProvider.broadcastTransaction(transaction.bitcoinSerialize()); } /** @@ -360,7 +356,24 @@ public abstract class Bitcoiny implements ForeignBlockchain { * @param key58 BIP32/HD extended Bitcoin private/public key * @return unspent BTC balance, or null if unable to determine balance */ - public Long getWalletBalance(String key58) { + public Long getWalletBalance(String key58) throws ForeignBlockchainException { + Long balance = 0L; + + List allUnspentOutputs = new ArrayList<>(); + Set walletAddresses = this.getWalletAddresses(key58); + for (String address : walletAddresses) { + allUnspentOutputs.addAll(this.getUnspentOutputs(address)); + } + for (TransactionOutput output : allUnspentOutputs) { + if (!output.isAvailableForSpending()) { + continue; + } + balance += output.getValue().value; + } + return balance; + } + + public Long getWalletBalanceFromBitcoinj(String key58) { Context.propagate(bitcoinjContext); Wallet wallet = walletFromDeterministicKey58(key58); @@ -375,7 +388,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { public Long getWalletBalanceFromTransactions(String key58) throws ForeignBlockchainException { long balance = 0; - Comparator oldestTimestampFirstComparator = Comparator.comparingInt(SimpleTransaction::getTimestamp); + Comparator oldestTimestampFirstComparator = Comparator.comparingLong(SimpleTransaction::getTimestamp); List transactions = getWalletTransactions(key58).stream().sorted(oldestTimestampFirstComparator).collect(Collectors.toList()); for (SimpleTransaction transaction : transactions) { balance += transaction.getTotalAmount(); @@ -409,9 +422,6 @@ public abstract class Bitcoiny implements ForeignBlockchain { Set walletTransactions = new HashSet<>(); Set keySet = new HashSet<>(); - // Set the number of consecutive empty batches required before giving up - final int numberOfAdditionalBatchesToSearch = 7; - int unusedCounter = 0; int ki = 0; do { @@ -438,12 +448,12 @@ public abstract class Bitcoiny implements ForeignBlockchain { if (areAllKeysUnused) { // No transactions - if (unusedCounter >= numberOfAdditionalBatchesToSearch) { + if (unusedCounter >= Settings.getInstance().getGapLimit()) { // ... and we've hit our search limit break; } // We haven't hit our search limit yet so increment the counter and keep looking - unusedCounter++; + unusedCounter += WALLET_KEY_LOOKAHEAD_INCREMENT; } else { // Some keys in this batch were used, so reset the counter unusedCounter = 0; @@ -455,7 +465,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { // Process new keys } while (true); - Comparator newestTimestampFirstComparator = Comparator.comparingInt(SimpleTransaction::getTimestamp).reversed(); + Comparator newestTimestampFirstComparator = Comparator.comparingLong(SimpleTransaction::getTimestamp).reversed(); // Update cache and return transactionsCacheTimestamp = NTP.getTime(); @@ -468,6 +478,64 @@ public abstract class Bitcoiny implements ForeignBlockchain { } } + public Set getWalletAddresses(String key58) throws ForeignBlockchainException { + synchronized (this) { + Context.propagate(bitcoinjContext); + + Wallet wallet = walletFromDeterministicKey58(key58); + DeterministicKeyChain keyChain = wallet.getActiveKeyChain(); + + keyChain.setLookaheadSize(Bitcoiny.WALLET_KEY_LOOKAHEAD_INCREMENT); + keyChain.maybeLookAhead(); + + List keys = new ArrayList<>(keyChain.getLeafKeys()); + + Set keySet = new HashSet<>(); + + int unusedCounter = 0; + int ki = 0; + do { + boolean areAllKeysUnused = true; + + for (; ki < keys.size(); ++ki) { + DeterministicKey dKey = keys.get(ki); + + // Check for transactions + Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH); + keySet.add(address.toString()); + byte[] script = ScriptBuilder.createOutputScript(address).getProgram(); + + // Ask for transaction history - if it's empty then key has never been used + List historicTransactionHashes = this.getAddressTransactions(script, false); + + if (!historicTransactionHashes.isEmpty()) { + areAllKeysUnused = false; + } + } + + if (areAllKeysUnused) { + // No transactions + if (unusedCounter >= Settings.getInstance().getGapLimit()) { + // ... and we've hit our search limit + break; + } + // We haven't hit our search limit yet so increment the counter and keep looking + unusedCounter += WALLET_KEY_LOOKAHEAD_INCREMENT; + } else { + // Some keys in this batch were used, so reset the counter + unusedCounter = 0; + } + + // Generate some more keys + keys.addAll(generateMoreKeys(keyChain)); + + // Process new keys + } while (true); + + return keySet; + } + } + protected SimpleTransaction convertToSimpleTransaction(BitcoinyTransaction t, Set keySet) { long amount = 0; long total = 0L; @@ -537,7 +605,8 @@ public abstract class Bitcoiny implements ForeignBlockchain { // All inputs and outputs relate to this wallet, so the balance should be unaffected amount = 0; } - return new SimpleTransaction(t.txHash, t.timestamp, amount, fee, inputs, outputs); + long timestampMillis = t.timestamp * 1000L; + return new SimpleTransaction(t.txHash, timestampMillis, amount, fee, inputs, outputs, null); } /** @@ -573,7 +642,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { Address address = Address.fromKey(this.params, dKey, ScriptType.P2PKH); byte[] script = ScriptBuilder.createOutputScript(address).getProgram(); - List unspentOutputs = this.blockchain.getUnspentOutputs(script, false); + List unspentOutputs = this.blockchainProvider.getUnspentOutputs(script, false); /* * If there are no unspent outputs then either: @@ -591,7 +660,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { } // Ask for transaction history - if it's empty then key has never been used - List historicTransactionHashes = this.blockchain.getAddressTransactions(script, false); + List historicTransactionHashes = this.blockchainProvider.getAddressTransactions(script, false); if (!historicTransactionHashes.isEmpty()) { // Fully spent key - case (a) @@ -629,7 +698,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { this.keyChain = this.wallet.getActiveKeyChain(); // Set up wallet's key chain - this.keyChain.setLookaheadSize(Bitcoiny.WALLET_KEY_LOOKAHEAD_INCREMENT_BITCOINJ); + this.keyChain.setLookaheadSize(Settings.getInstance().getBitcoinjLookaheadSize()); this.keyChain.maybeLookAhead(); } @@ -650,7 +719,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { List unspentOutputs; try { - unspentOutputs = this.bitcoiny.blockchain.getUnspentOutputs(script, false); + unspentOutputs = this.bitcoiny.blockchainProvider.getUnspentOutputs(script, false); } catch (ForeignBlockchainException e) { throw new UTXOProviderException(String.format("Unable to fetch unspent outputs for %s", address)); } @@ -674,7 +743,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { // Ask for transaction history - if it's empty then key has never been used List historicTransactionHashes; try { - historicTransactionHashes = this.bitcoiny.blockchain.getAddressTransactions(script, false); + historicTransactionHashes = this.bitcoiny.blockchainProvider.getAddressTransactions(script, false); } catch (ForeignBlockchainException e) { throw new UTXOProviderException(String.format("Unable to fetch transaction history for %s", address)); } @@ -727,7 +796,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { @Override public int getChainHeadHeight() throws UTXOProviderException { try { - return this.bitcoiny.blockchain.getCurrentHeight(); + return this.bitcoiny.blockchainProvider.getCurrentHeight(); } catch (ForeignBlockchainException e) { throw new UTXOProviderException("Unable to determine Bitcoiny chain height"); } diff --git a/src/main/java/org/qortal/crosschain/BitcoinyBlockchainProvider.java b/src/main/java/org/qortal/crosschain/BitcoinyBlockchainProvider.java index 7691efb1..8075bfff 100644 --- a/src/main/java/org/qortal/crosschain/BitcoinyBlockchainProvider.java +++ b/src/main/java/org/qortal/crosschain/BitcoinyBlockchainProvider.java @@ -1,5 +1,7 @@ package org.qortal.crosschain; +import cash.z.wallet.sdk.rpc.CompactFormats.*; + import java.util.List; public abstract class BitcoinyBlockchainProvider { @@ -7,18 +9,32 @@ public abstract class BitcoinyBlockchainProvider { public static final boolean INCLUDE_UNCONFIRMED = true; public static final boolean EXCLUDE_UNCONFIRMED = false; + /** Sets the blockchain using this provider instance */ + public abstract void setBlockchain(Bitcoiny blockchain); + /** Returns ID unique to bitcoiny network (e.g. "Litecoin-TEST3") */ public abstract String getNetId(); /** Returns current blockchain height. */ public abstract int getCurrentHeight() throws ForeignBlockchainException; + /** Returns a list of compact blocks, starting at startHeight (inclusive), up to count max. + * Used for Pirate/Zcash only. If ever needed for other blockchains, the response format will need to be + * made generic. */ + public abstract List getCompactBlocks(int startHeight, int count) throws ForeignBlockchainException; + /** Returns a list of raw block headers, starting at startHeight (inclusive), up to count max. */ public abstract List getRawBlockHeaders(int startHeight, int count) throws ForeignBlockchainException; + /** Returns a list of block timestamps, starting at startHeight (inclusive), up to count max. */ + public abstract List getBlockTimestamps(int startHeight, int count) throws ForeignBlockchainException; + /** Returns balance of address represented by scriptPubKey. */ public abstract long getConfirmedBalance(byte[] scriptPubKey) throws ForeignBlockchainException; + /** Returns balance of base58 encoded address. */ + public abstract long getConfirmedAddressBalance(String base58Address) throws ForeignBlockchainException; + /** Returns raw, serialized, transaction bytes given txHash. */ public abstract byte[] getRawTransaction(String txHash) throws ForeignBlockchainException; @@ -31,6 +47,12 @@ public abstract class BitcoinyBlockchainProvider { /** Returns list of transaction hashes (and heights) for address represented by scriptPubKey, optionally including unconfirmed transactions. */ public abstract List getAddressTransactions(byte[] scriptPubKey, boolean includeUnconfirmed) throws ForeignBlockchainException; + /** Returns list of BitcoinyTransaction objects for address, optionally including unconfirmed transactions. */ + public abstract List getAddressBitcoinyTransactions(String address, boolean includeUnconfirmed) throws ForeignBlockchainException; + + /** Returns list of unspent transaction outputs for address, optionally including unconfirmed transactions. */ + public abstract List getUnspentOutputs(String address, boolean includeUnconfirmed) throws ForeignBlockchainException; + /** Returns list of unspent transaction outputs for address represented by scriptPubKey, optionally including unconfirmed transactions. */ public abstract List getUnspentOutputs(byte[] scriptPubKey, boolean includeUnconfirmed) throws ForeignBlockchainException; diff --git a/src/main/java/org/qortal/crosschain/BitcoinyTransaction.java b/src/main/java/org/qortal/crosschain/BitcoinyTransaction.java index caf0b36d..90ee2b23 100644 --- a/src/main/java/org/qortal/crosschain/BitcoinyTransaction.java +++ b/src/main/java/org/qortal/crosschain/BitcoinyTransaction.java @@ -1,5 +1,6 @@ package org.qortal.crosschain; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -10,8 +11,13 @@ import javax.xml.bind.annotation.XmlTransient; @XmlAccessorType(XmlAccessType.FIELD) public class BitcoinyTransaction { + public static final Comparator CONFIRMED_FIRST = (a, b) -> Boolean.compare(a.height != 0, b.height != 0); + public final String txHash; + @XmlTransient + public Integer height; + @XmlTransient public final int size; @@ -113,6 +119,10 @@ public class BitcoinyTransaction { this.totalAmount = outputs.stream().map(output -> output.value).reduce(0L, Long::sum); } + public int getHeight() { + return this.height; + } + public String toString() { return String.format("txHash %s, size %d, locktime %d, timestamp %d\n" + "\tinputs: [%s]\n" diff --git a/src/main/java/org/qortal/crosschain/Digibyte.java b/src/main/java/org/qortal/crosschain/Digibyte.java index 3ab5e78e..4358b3b3 100644 --- a/src/main/java/org/qortal/crosschain/Digibyte.java +++ b/src/main/java/org/qortal/crosschain/Digibyte.java @@ -134,6 +134,8 @@ public class Digibyte extends Bitcoiny { Context bitcoinjContext = new Context(digibyteNet.getParams()); instance = new Digibyte(digibyteNet, electrumX, bitcoinjContext, CURRENCY_CODE); + + electrumX.setBlockchain(instance); } return instance; diff --git a/src/main/java/org/qortal/crosschain/Dogecoin.java b/src/main/java/org/qortal/crosschain/Dogecoin.java index 219c762c..9af8d990 100644 --- a/src/main/java/org/qortal/crosschain/Dogecoin.java +++ b/src/main/java/org/qortal/crosschain/Dogecoin.java @@ -47,7 +47,8 @@ public class Dogecoin extends Bitcoiny { // Servers chosen on NO BASIS WHATSOEVER from various sources! new Server("electrum1.cipig.net", ConnectionType.SSL, 20060), new Server("electrum2.cipig.net", ConnectionType.SSL, 20060), - new Server("electrum3.cipig.net", ConnectionType.SSL, 20060)); + new Server("electrum3.cipig.net", ConnectionType.SSL, 20060), + new Server("161.97.137.235", ConnectionType.SSL, 50002)); // TODO: add more mainnet servers. It's too centralized. } @@ -135,6 +136,8 @@ public class Dogecoin extends Bitcoiny { Context bitcoinjContext = new Context(dogecoinNet.getParams()); instance = new Dogecoin(dogecoinNet, electrumX, bitcoinjContext, CURRENCY_CODE); + + electrumX.setBlockchain(instance); } return instance; diff --git a/src/main/java/org/qortal/crosschain/ElectrumX.java b/src/main/java/org/qortal/crosschain/ElectrumX.java index 6d6cfb15..a331b111 100644 --- a/src/main/java/org/qortal/crosschain/ElectrumX.java +++ b/src/main/java/org/qortal/crosschain/ElectrumX.java @@ -5,12 +5,14 @@ import java.math.BigDecimal; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; +import java.text.DecimalFormat; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLSocketFactory; +import cash.z.wallet.sdk.rpc.CompactFormats.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.json.simple.JSONArray; @@ -29,7 +31,11 @@ public class ElectrumX extends BitcoinyBlockchainProvider { private static final Logger LOGGER = LogManager.getLogger(ElectrumX.class); private static final Random RANDOM = new Random(); + // See: https://electrumx.readthedocs.io/en/latest/protocol-changes.html private static final double MIN_PROTOCOL_VERSION = 1.2; + private static final double MAX_PROTOCOL_VERSION = 2.0; // Higher than current latest, for hopeful future-proofing + private static final String CLIENT_NAME = "Qortal"; + private static final int BLOCK_HEADER_LENGTH = 80; // "message": "daemon error: DaemonError({'code': -5, 'message': 'No such mempool or blockchain transaction. Use gettransaction for wallet transactions.'})" @@ -39,7 +45,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider { private static final String VERBOSE_TRANSACTIONS_UNSUPPORTED_MESSAGE = "verbose transactions are currently unsupported"; private static final int RESPONSE_TIME_READINGS = 5; - private static final long MAX_AVG_RESPONSE_TIME = 500L; // ms + private static final long MAX_AVG_RESPONSE_TIME = 1000L; // ms public static class Server { String hostname; @@ -107,6 +113,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider { private final String netId; private final String expectedGenesisHash; private final Map defaultPorts = new EnumMap<>(Server.ConnectionType.class); + private Bitcoiny blockchain; private final Object serverLock = new Object(); private Server currentServer; @@ -135,6 +142,11 @@ public class ElectrumX extends BitcoinyBlockchainProvider { // Methods for use by other classes + @Override + public void setBlockchain(Bitcoiny blockchain) { + this.blockchain = blockchain; + } + @Override public String getNetId() { return this.netId; @@ -161,6 +173,16 @@ public class ElectrumX extends BitcoinyBlockchainProvider { return ((Long) heightObj).intValue(); } + /** + * Returns list of raw blocks, starting from startHeight inclusive. + *

      + * @throws ForeignBlockchainException if error occurs + */ + @Override + public List getCompactBlocks(int startHeight, int count) throws ForeignBlockchainException { + throw new ForeignBlockchainException("getCompactBlocks not implemented for ElectrumX due to being specific to zcash"); + } + /** * Returns list of raw block headers, starting from startHeight inclusive. *

      @@ -222,6 +244,17 @@ public class ElectrumX extends BitcoinyBlockchainProvider { return rawBlockHeaders; } + /** + * Returns list of raw block timestamps, starting from startHeight inclusive. + *

      + * @throws ForeignBlockchainException if error occurs + */ + @Override + public List getBlockTimestamps(int startHeight, int count) throws ForeignBlockchainException { + // FUTURE: implement this if needed. For now we use getRawBlockHeaders directly + throw new ForeignBlockchainException("getBlockTimestamps not yet implemented for ElectrumX"); + } + /** * Returns confirmed balance, based on passed payment script. *

      @@ -247,6 +280,29 @@ public class ElectrumX extends BitcoinyBlockchainProvider { return (Long) balanceJson.get("confirmed"); } + /** + * Returns confirmed balance, based on passed base58 encoded address. + *

      + * @return confirmed balance, or zero if address unknown + * @throws ForeignBlockchainException if there was an error + */ + @Override + public long getConfirmedAddressBalance(String base58Address) throws ForeignBlockchainException { + throw new ForeignBlockchainException("getConfirmedAddressBalance not yet implemented for ElectrumX"); + } + + /** + * Returns list of unspent outputs pertaining to passed address. + *

      + * @return list of unspent outputs, or empty list if address unknown + * @throws ForeignBlockchainException if there was an error. + */ + @Override + public List getUnspentOutputs(String address, boolean includeUnconfirmed) throws ForeignBlockchainException { + byte[] script = this.blockchain.addressToScriptPubKey(address); + return this.getUnspentOutputs(script, includeUnconfirmed); + } + /** * Returns list of unspent outputs pertaining to passed payment script. *

      @@ -482,6 +538,12 @@ public class ElectrumX extends BitcoinyBlockchainProvider { return transactionHashes; } + @Override + public List getAddressBitcoinyTransactions(String address, boolean includeUnconfirmed) throws ForeignBlockchainException { + // FUTURE: implement this if needed. For now we use getAddressTransactions() + getTransaction() + throw new ForeignBlockchainException("getAddressBitcoinyTransactions not yet implemented for ElectrumX"); + } + /** * Broadcasts raw transaction to network. *

      @@ -622,6 +684,9 @@ public class ElectrumX extends BitcoinyBlockchainProvider { this.scanner = new Scanner(this.socket.getInputStream()); this.scanner.useDelimiter("\n"); + // All connections need to start with a version negotiation + this.connectedRpc("server.version"); + // Check connection is suitable by asking for server features, including genesis block hash JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features"); @@ -668,6 +733,17 @@ public class ElectrumX extends BitcoinyBlockchainProvider { JSONArray requestParams = new JSONArray(); requestParams.addAll(Arrays.asList(params)); + + // server.version needs additional params to negotiate a version + if (method.equals("server.version")) { + requestParams.add(CLIENT_NAME); + List versions = new ArrayList<>(); + DecimalFormat df = new DecimalFormat("#.#"); + versions.add(df.format(MIN_PROTOCOL_VERSION)); + versions.add(df.format(MAX_PROTOCOL_VERSION)); + requestParams.add(versions); + } + requestJson.put("params", requestParams); String request = requestJson.toJSONString() + "\n"; @@ -682,6 +758,10 @@ public class ElectrumX extends BitcoinyBlockchainProvider { } catch (IOException | NoSuchElementException e) { // Unable to send, or receive -- try another server? return null; + } catch (NoSuchMethodError e) { + // Likely an SSL dependency issue - retries are unlikely to succeed + LOGGER.error("ElectrumX output stream error", e); + return null; } long endTime = System.currentTimeMillis(); diff --git a/src/main/java/org/qortal/crosschain/LegacyZcashAddress.java b/src/main/java/org/qortal/crosschain/LegacyZcashAddress.java new file mode 100644 index 00000000..14958242 --- /dev/null +++ b/src/main/java/org/qortal/crosschain/LegacyZcashAddress.java @@ -0,0 +1,254 @@ +/* + * Copyright 2011 Google Inc. + * Copyright 2014 Giannis Dzegoutanis + * Copyright 2015 Andreas Schildbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Updated for Zcash in May 2022 by Qortal core dev team. Modifications allow +* correct encoding of P2SH (t3) addresses only. */ + +package org.qortal.crosschain; + +import org.bitcoinj.core.*; +import org.bitcoinj.params.Networks; +import org.bitcoinj.script.Script.ScriptType; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Objects; + +/** + *

      A Bitcoin address looks like 1MsScoe2fTJoq4ZPdQgqyhgWeoNamYPevy and is derived from an elliptic curve public key + * plus a set of network parameters. Not to be confused with a {@link PeerAddress} or {@link AddressMessage} + * which are about network (TCP) addresses.

      + * + *

      A standard address is built by taking the RIPE-MD160 hash of the public key bytes, with a version prefix and a + * checksum suffix, then encoding it textually as base58. The version prefix is used to both denote the network for + * which the address is valid (see {@link NetworkParameters}, and also to indicate how the bytes inside the address + * should be interpreted. Whilst almost all addresses today are hashes of public keys, another (currently unsupported + * type) can contain a hash of a script instead.

      + */ +public class LegacyZcashAddress extends Address { + /** + * An address is a RIPEMD160 hash of a public key, therefore is always 160 bits or 20 bytes. + */ + public static final int LENGTH = 20; + + /** True if P2SH, false if P2PKH. */ + public final boolean p2sh; + + /* Zcash P2SH header bytes */ + private static int P2SH_HEADER_1 = 28; + private static int P2SH_HEADER_2 = 189; + + /** + * Private constructor. Use {@link #fromBase58(NetworkParameters, String)}, + * {@link #fromPubKeyHash(NetworkParameters, byte[])}, {@link #fromScriptHash(NetworkParameters, byte[])} or + * {@link #fromKey(NetworkParameters, ECKey)}. + * + * @param params + * network this address is valid for + * @param p2sh + * true if hash160 is hash of a script, false if it is hash of a pubkey + * @param hash160 + * 20-byte hash of pubkey or script + */ + private LegacyZcashAddress(NetworkParameters params, boolean p2sh, byte[] hash160) throws AddressFormatException { + super(params, hash160); + if (hash160.length != 20) + throw new AddressFormatException.InvalidDataLength( + "Legacy addresses are 20 byte (160 bit) hashes, but got: " + hash160.length); + this.p2sh = p2sh; + } + + /** + * Construct a {@link LegacyZcashAddress} that represents the given pubkey hash. The resulting address will be a P2PKH type of + * address. + * + * @param params + * network this address is valid for + * @param hash160 + * 20-byte pubkey hash + * @return constructed address + */ + public static LegacyZcashAddress fromPubKeyHash(NetworkParameters params, byte[] hash160) throws AddressFormatException { + return new LegacyZcashAddress(params, false, hash160); + } + + /** + * Construct a {@link LegacyZcashAddress} that represents the public part of the given {@link ECKey}. Note that an address is + * derived from a hash of the public key and is not the public key itself. + * + * @param params + * network this address is valid for + * @param key + * only the public part is used + * @return constructed address + */ + public static LegacyZcashAddress fromKey(NetworkParameters params, ECKey key) { + return fromPubKeyHash(params, key.getPubKeyHash()); + } + + /** + * Construct a {@link LegacyZcashAddress} that represents the given P2SH script hash. + * + * @param params + * network this address is valid for + * @param hash160 + * P2SH script hash + * @return constructed address + */ + public static LegacyZcashAddress fromScriptHash(NetworkParameters params, byte[] hash160) throws AddressFormatException { + return new LegacyZcashAddress(params, true, hash160); + } + + /** + * Construct a {@link LegacyZcashAddress} from its base58 form. + * + * @param params + * expected network this address is valid for, or null if if the network should be derived from the + * base58 + * @param base58 + * base58-encoded textual form of the address + * @throws AddressFormatException + * if the given base58 doesn't parse or the checksum is invalid + * @throws AddressFormatException.WrongNetwork + * if the given address is valid but for a different chain (eg testnet vs mainnet) + */ + public static LegacyZcashAddress fromBase58(@Nullable NetworkParameters params, String base58) + throws AddressFormatException, AddressFormatException.WrongNetwork { + byte[] versionAndDataBytes = Base58.decodeChecked(base58); + int version = versionAndDataBytes[0] & 0xFF; + byte[] bytes = Arrays.copyOfRange(versionAndDataBytes, 1, versionAndDataBytes.length); + if (params == null) { + for (NetworkParameters p : Networks.get()) { + if (version == p.getAddressHeader()) + return new LegacyZcashAddress(p, false, bytes); + else if (version == p.getP2SHHeader()) + return new LegacyZcashAddress(p, true, bytes); + } + throw new AddressFormatException.InvalidPrefix("No network found for " + base58); + } else { + if (version == params.getAddressHeader()) + return new LegacyZcashAddress(params, false, bytes); + else if (version == params.getP2SHHeader()) + return new LegacyZcashAddress(params, true, bytes); + throw new AddressFormatException.WrongNetwork(version); + } + } + + /** + * Get the version header of an address. This is the first byte of a base58 encoded address. + * + * @return version header as one byte + */ + public int getVersion() { + return p2sh ? params.getP2SHHeader() : params.getAddressHeader(); + } + + /** + * Returns the base58-encoded textual form, including version and checksum bytes. + * + * @return textual form + */ + public String toBase58() { + return this.encodeChecked(getVersion(), bytes); + } + + /** The (big endian) 20 byte hash that is the core of a Bitcoin address. */ + @Override + public byte[] getHash() { + return bytes; + } + + /** + * Get the type of output script that will be used for sending to the address. This is either + * {@link ScriptType#P2PKH} or {@link ScriptType#P2SH}. + * + * @return type of output script + */ + @Override + public ScriptType getOutputScriptType() { + return p2sh ? ScriptType.P2SH : ScriptType.P2PKH; + } + + /** + * Given an address, examines the version byte and attempts to find a matching NetworkParameters. If you aren't sure + * which network the address is intended for (eg, it was provided by a user), you can use this to decide if it is + * compatible with the current wallet. + * + * @return network the address is valid for + * @throws AddressFormatException if the given base58 doesn't parse or the checksum is invalid + */ + public static NetworkParameters getParametersFromAddress(String address) throws AddressFormatException { + return LegacyZcashAddress.fromBase58(null, address).getParameters(); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + LegacyZcashAddress other = (LegacyZcashAddress) o; + return super.equals(other) && this.p2sh == other.p2sh; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), p2sh); + } + + @Override + public String toString() { + return toBase58(); + } + + @Override + public LegacyZcashAddress clone() throws CloneNotSupportedException { + return (LegacyZcashAddress) super.clone(); + } + + public static String encodeChecked(int version, byte[] payload) { + if (version < 0 || version > 255) + throw new IllegalArgumentException("Version not in range."); + + // A stringified buffer is: + // 1 byte version + data bytes + 4 bytes check code (a truncated hash) + byte[] addressBytes = new byte[2 + payload.length + 4]; + addressBytes[0] = (byte) P2SH_HEADER_1; + addressBytes[1] = (byte) P2SH_HEADER_2; + System.arraycopy(payload, 0, addressBytes, 2, payload.length); + byte[] checksum = Sha256Hash.hashTwice(addressBytes, 0, payload.length + 2); + System.arraycopy(checksum, 0, addressBytes, payload.length + 2, 4); + return Base58.encode(addressBytes); + } + +// // Comparator for LegacyAddress, left argument must be LegacyAddress, right argument can be any Address +// private static final Comparator
      LEGACY_ADDRESS_COMPARATOR = Address.PARTIAL_ADDRESS_COMPARATOR +// .thenComparingInt(a -> ((LegacyZcashAddress) a).getVersion()) // Then compare Legacy address version byte +// .thenComparing(a -> a.bytes, UnsignedBytes.lexicographicalComparator()); // Then compare Legacy bytes +// +// /** +// * {@inheritDoc} +// * +// * @param o other {@code Address} object +// * @return comparison result +// */ +// @Override +// public int compareTo(Address o) { +// return LEGACY_ADDRESS_COMPARATOR.compare(this, o); +// } +} diff --git a/src/main/java/org/qortal/crosschain/Litecoin.java b/src/main/java/org/qortal/crosschain/Litecoin.java index 02dd466f..6fc6ba50 100644 --- a/src/main/java/org/qortal/crosschain/Litecoin.java +++ b/src/main/java/org/qortal/crosschain/Litecoin.java @@ -54,7 +54,8 @@ public class Litecoin extends Bitcoiny { new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063), new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063), new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022), - new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002)); + new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002), + new Server("62.171.169.176", Server.ConnectionType.SSL, 50002)); } @Override @@ -145,6 +146,8 @@ public class Litecoin extends Bitcoiny { Context bitcoinjContext = new Context(litecoinNet.getParams()); instance = new Litecoin(litecoinNet, electrumX, bitcoinjContext, CURRENCY_CODE); + + electrumX.setBlockchain(instance); } return instance; diff --git a/src/main/java/org/qortal/crosschain/LitecoinACCTv2.java b/src/main/java/org/qortal/crosschain/LitecoinACCTv2.java deleted file mode 100644 index c5728953..00000000 --- a/src/main/java/org/qortal/crosschain/LitecoinACCTv2.java +++ /dev/null @@ -1,854 +0,0 @@ -package org.qortal.crosschain; - -import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -import org.ciyam.at.*; -import org.qortal.account.Account; -import org.qortal.asset.Asset; -import org.qortal.at.QortalFunctionCode; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.utils.Base58; -import org.qortal.utils.BitTwiddling; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.List; - -import static org.ciyam.at.OpCode.calcOffset; - -/** - * Cross-chain trade AT - * - *

      - *

        - *
      • Bob generates Litecoin & Qortal 'trade' keys - *
          - *
        • private key required to sign P2SH redeem tx
        • - *
        • private key could be used to create 'secret' (e.g. double-SHA256)
        • - *
        • encrypted private key could be stored in Qortal AT for access by Bob from any node
        • - *
        - *
      • - *
      • Bob deploys Qortal AT - *
          - *
        - *
      • - *
      • Alice finds Qortal AT and wants to trade - *
          - *
        • Alice generates Litecoin & Qortal 'trade' keys
        • - *
        • Alice funds Litecoin P2SH-A
        • - *
        • Alice sends 'offer' MESSAGE to Bob from her Qortal trade address, containing: - *
            - *
          • hash-of-secret-A
          • - *
          • her 'trade' Litecoin PKH
          • - *
          - *
        • - *
        - *
      • - *
      • Bob receives "offer" MESSAGE - *
          - *
        • Checks Alice's P2SH-A
        • - *
        • Sends 'trade' MESSAGE to Qortal AT from his trade address, containing: - *
            - *
          • Alice's trade Qortal address
          • - *
          • Alice's trade Litecoin PKH
          • - *
          • hash-of-secret-A
          • - *
          - *
        • - *
        - *
      • - *
      • Alice checks Qortal AT to confirm it's locked to her - *
          - *
        • Alice sends 'redeem' MESSAGE to Qortal AT from her trade address, containing: - *
            - *
          • secret-A
          • - *
          • Qortal receiving address of her chosing
          • - *
          - *
        • - *
        • AT's QORT funds are sent to Qortal receiving address
        • - *
        - *
      • - *
      • Bob checks AT, extracts secret-A - *
          - *
        • Bob redeems P2SH-A using his Litecoin trade key and secret-A
        • - *
        • P2SH-A LTC funds end up at Litecoin address determined by redeem transaction output(s)
        • - *
        - *
      • - *
      - */ -public class LitecoinACCTv2 implements ACCT { - - public static final String NAME = LitecoinACCTv2.class.getSimpleName(); - public static final byte[] CODE_BYTES_HASH = HashCode.fromString("d5ea386a41441180c854ca8d7bbc620bfd53a97df2650a2b162b52324caf6e19").asBytes(); // SHA256 of AT code bytes - - public static final int SECRET_LENGTH = 32; - - /** Value offset into AT segment where 'mode' variable (long) is stored. (Multiply by MachineState.VALUE_SIZE for byte offset). */ - private static final int MODE_VALUE_OFFSET = 61; - /** Byte offset into AT state data where 'mode' variable (long) is stored. */ - public static final int MODE_BYTE_OFFSET = MachineState.HEADER_LENGTH + (MODE_VALUE_OFFSET * MachineState.VALUE_SIZE); - - public static class OfferMessageData { - public byte[] partnerLitecoinPKH; - public byte[] hashOfSecretA; - public long lockTimeA; - } - public static final int OFFER_MESSAGE_LENGTH = 20 /*partnerLitecoinPKH*/ + 20 /*hashOfSecretA*/ + 8 /*lockTimeA*/; - public static final int TRADE_MESSAGE_LENGTH = 32 /*partner's Qortal trade address (padded from 25 to 32)*/ - + 24 /*partner's Litecoin PKH (padded from 20 to 24)*/ - + 8 /*AT trade timeout (minutes)*/ - + 24 /*hash of secret-A (padded from 20 to 24)*/ - + 8 /*lockTimeA*/; - public static final int REDEEM_MESSAGE_LENGTH = 32 /*secret-A*/ + 32 /*partner's Qortal receiving address padded from 25 to 32*/; - public static final int CANCEL_MESSAGE_LENGTH = 32 /*AT creator's Qortal address*/; - - private static LitecoinACCTv2 instance; - - private LitecoinACCTv2() { - } - - public static synchronized LitecoinACCTv2 getInstance() { - if (instance == null) - instance = new LitecoinACCTv2(); - - return instance; - } - - @Override - public byte[] getCodeBytesHash() { - return CODE_BYTES_HASH; - } - - @Override - public int getModeByteOffset() { - return MODE_BYTE_OFFSET; - } - - @Override - public ForeignBlockchain getBlockchain() { - return Litecoin.getInstance(); - } - - /** - * Returns Qortal AT creation bytes for cross-chain trading AT. - *

      - * tradeTimeout (minutes) is the time window for the trade partner to send the - * 32-byte secret to the AT, before the AT automatically refunds the AT's creator. - * - * @param creatorTradeAddress AT creator's trade Qortal address - * @param litecoinPublicKeyHash 20-byte HASH160 of creator's trade Litecoin public key - * @param qortAmount how much QORT to pay trade partner if they send correct 32-byte secrets to AT - * @param litecoinAmount how much LTC the AT creator is expecting to trade - * @param tradeTimeout suggested timeout for entire trade - */ - public static byte[] buildQortalAT(String creatorTradeAddress, byte[] litecoinPublicKeyHash, long qortAmount, long litecoinAmount, int tradeTimeout) { - if (litecoinPublicKeyHash.length != 20) - throw new IllegalArgumentException("Litecoin public key hash should be 20 bytes"); - - // Labels for data segment addresses - int addrCounter = 0; - - // Constants (with corresponding dataByteBuffer.put*() calls below) - - final int addrCreatorTradeAddress1 = addrCounter++; - final int addrCreatorTradeAddress2 = addrCounter++; - final int addrCreatorTradeAddress3 = addrCounter++; - final int addrCreatorTradeAddress4 = addrCounter++; - - final int addrLitecoinPublicKeyHash = addrCounter; - addrCounter += 4; - - final int addrQortAmount = addrCounter++; - final int addrLitecoinAmount = addrCounter++; - final int addrTradeTimeout = addrCounter++; - - final int addrMessageTxnType = addrCounter++; - final int addrExpectedTradeMessageLength = addrCounter++; - final int addrExpectedRedeemMessageLength = addrCounter++; - - final int addrCreatorAddressPointer = addrCounter++; - final int addrQortalPartnerAddressPointer = addrCounter++; - final int addrMessageSenderPointer = addrCounter++; - - final int addrTradeMessagePartnerLitecoinPKHOffset = addrCounter++; - final int addrPartnerLitecoinPKHPointer = addrCounter++; - final int addrTradeMessageHashOfSecretAOffset = addrCounter++; - final int addrHashOfSecretAPointer = addrCounter++; - - final int addrRedeemMessageReceivingAddressOffset = addrCounter++; - - final int addrMessageDataPointer = addrCounter++; - final int addrMessageDataLength = addrCounter++; - - final int addrPartnerReceivingAddressPointer = addrCounter++; - - final int addrEndOfConstants = addrCounter; - - // Variables - - final int addrCreatorAddress1 = addrCounter++; - final int addrCreatorAddress2 = addrCounter++; - final int addrCreatorAddress3 = addrCounter++; - final int addrCreatorAddress4 = addrCounter++; - - final int addrQortalPartnerAddress1 = addrCounter++; - final int addrQortalPartnerAddress2 = addrCounter++; - final int addrQortalPartnerAddress3 = addrCounter++; - final int addrQortalPartnerAddress4 = addrCounter++; - - final int addrLockTimeA = addrCounter++; - final int addrRefundTimeout = addrCounter++; - final int addrRefundTimestamp = addrCounter++; - final int addrLastTxnTimestamp = addrCounter++; - final int addrBlockTimestamp = addrCounter++; - final int addrTxnType = addrCounter++; - final int addrResult = addrCounter++; - - final int addrMessageSender1 = addrCounter++; - final int addrMessageSender2 = addrCounter++; - final int addrMessageSender3 = addrCounter++; - final int addrMessageSender4 = addrCounter++; - - final int addrMessageLength = addrCounter++; - - final int addrMessageData = addrCounter; - addrCounter += 4; - - final int addrHashOfSecretA = addrCounter; - addrCounter += 4; - - final int addrPartnerLitecoinPKH = addrCounter; - addrCounter += 4; - - final int addrPartnerReceivingAddress = addrCounter; - addrCounter += 4; - - final int addrMode = addrCounter++; - assert addrMode == MODE_VALUE_OFFSET : String.format("addrMode %d does not match MODE_VALUE_OFFSET %d", addrMode, MODE_VALUE_OFFSET); - - // Data segment - ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE); - - // AT creator's trade Qortal address, decoded from Base58 - assert dataByteBuffer.position() == addrCreatorTradeAddress1 * MachineState.VALUE_SIZE : "addrCreatorTradeAddress1 incorrect"; - byte[] creatorTradeAddressBytes = Base58.decode(creatorTradeAddress); - dataByteBuffer.put(Bytes.ensureCapacity(creatorTradeAddressBytes, 32, 0)); - - // Litecoin public key hash - assert dataByteBuffer.position() == addrLitecoinPublicKeyHash * MachineState.VALUE_SIZE : "addrLitecoinPublicKeyHash incorrect"; - dataByteBuffer.put(Bytes.ensureCapacity(litecoinPublicKeyHash, 32, 0)); - - // Redeem Qort amount - assert dataByteBuffer.position() == addrQortAmount * MachineState.VALUE_SIZE : "addrQortAmount incorrect"; - dataByteBuffer.putLong(qortAmount); - - // Expected Litecoin amount - assert dataByteBuffer.position() == addrLitecoinAmount * MachineState.VALUE_SIZE : "addrLitecoinAmount incorrect"; - dataByteBuffer.putLong(litecoinAmount); - - // Suggested trade timeout (minutes) - assert dataByteBuffer.position() == addrTradeTimeout * MachineState.VALUE_SIZE : "addrTradeTimeout incorrect"; - dataByteBuffer.putLong(tradeTimeout); - - // We're only interested in MESSAGE transactions - assert dataByteBuffer.position() == addrMessageTxnType * MachineState.VALUE_SIZE : "addrMessageTxnType incorrect"; - dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value); - - // Expected length of 'trade' MESSAGE data from AT creator - assert dataByteBuffer.position() == addrExpectedTradeMessageLength * MachineState.VALUE_SIZE : "addrExpectedTradeMessageLength incorrect"; - dataByteBuffer.putLong(TRADE_MESSAGE_LENGTH); - - // Expected length of 'redeem' MESSAGE data from trade partner - assert dataByteBuffer.position() == addrExpectedRedeemMessageLength * MachineState.VALUE_SIZE : "addrExpectedRedeemMessageLength incorrect"; - dataByteBuffer.putLong(REDEEM_MESSAGE_LENGTH); - - // Index into data segment of AT creator's address, used by GET_B_IND - assert dataByteBuffer.position() == addrCreatorAddressPointer * MachineState.VALUE_SIZE : "addrCreatorAddressPointer incorrect"; - dataByteBuffer.putLong(addrCreatorAddress1); - - // Index into data segment of partner's Qortal address, used by SET_B_IND - assert dataByteBuffer.position() == addrQortalPartnerAddressPointer * MachineState.VALUE_SIZE : "addrQortalPartnerAddressPointer incorrect"; - dataByteBuffer.putLong(addrQortalPartnerAddress1); - - // Index into data segment of (temporary) transaction's sender's address, used by GET_B_IND - assert dataByteBuffer.position() == addrMessageSenderPointer * MachineState.VALUE_SIZE : "addrMessageSenderPointer incorrect"; - dataByteBuffer.putLong(addrMessageSender1); - - // Offset into 'trade' MESSAGE data payload for extracting partner's Litecoin PKH - assert dataByteBuffer.position() == addrTradeMessagePartnerLitecoinPKHOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerLitecoinPKHOffset incorrect"; - dataByteBuffer.putLong(32L); - - // Index into data segment of partner's Litecoin PKH, used by GET_B_IND - assert dataByteBuffer.position() == addrPartnerLitecoinPKHPointer * MachineState.VALUE_SIZE : "addrPartnerLitecoinPKHPointer incorrect"; - dataByteBuffer.putLong(addrPartnerLitecoinPKH); - - // Offset into 'trade' MESSAGE data payload for extracting hash-of-secret-A - assert dataByteBuffer.position() == addrTradeMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrTradeMessageHashOfSecretAOffset incorrect"; - dataByteBuffer.putLong(64L); - - // Index into data segment to hash of secret A, used by GET_B_IND - assert dataByteBuffer.position() == addrHashOfSecretAPointer * MachineState.VALUE_SIZE : "addrHashOfSecretAPointer incorrect"; - dataByteBuffer.putLong(addrHashOfSecretA); - - // Offset into 'redeem' MESSAGE data payload for extracting Qortal receiving address - assert dataByteBuffer.position() == addrRedeemMessageReceivingAddressOffset * MachineState.VALUE_SIZE : "addrRedeemMessageReceivingAddressOffset incorrect"; - dataByteBuffer.putLong(32L); - - // Source location and length for hashing any passed secret - assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect"; - dataByteBuffer.putLong(addrMessageData); - assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect"; - dataByteBuffer.putLong(32L); - - // Pointer into data segment of where to save partner's receiving Qortal address, used by GET_B_IND - assert dataByteBuffer.position() == addrPartnerReceivingAddressPointer * MachineState.VALUE_SIZE : "addrPartnerReceivingAddressPointer incorrect"; - dataByteBuffer.putLong(addrPartnerReceivingAddress); - - assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants"; - - // Code labels - Integer labelRefund = null; - - Integer labelTradeTxnLoop = null; - Integer labelCheckTradeTxn = null; - Integer labelCheckCancelTxn = null; - Integer labelNotTradeNorCancelTxn = null; - Integer labelCheckNonRefundTradeTxn = null; - Integer labelTradeTxnExtract = null; - Integer labelRedeemTxnLoop = null; - Integer labelCheckRedeemTxn = null; - Integer labelCheckRedeemTxnSender = null; - Integer labelPayout = null; - - ByteBuffer codeByteBuffer = ByteBuffer.allocate(768); - - // Two-pass version - for (int pass = 0; pass < 2; ++pass) { - codeByteBuffer.clear(); - - try { - /* Initialization */ - - // Use AT creation 'timestamp' as starting point for finding transactions sent to AT - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxnTimestamp)); - - // Load B register with AT creator's address so we can save it into addrCreatorAddress1-4 - codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B)); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrCreatorAddressPointer)); - - // Set restart position to after this opcode - codeByteBuffer.put(OpCode.SET_PCS.compile()); - - /* Loop, waiting for message from AT creator's trade address containing trade partner details, or AT owner's address to cancel offer */ - - /* Transaction processing loop */ - labelTradeTxnLoop = codeByteBuffer.position(); - - /* Sleep until message arrives */ - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.SLEEP_UNTIL_MESSAGE.value, addrLastTxnTimestamp)); - - // Find next transaction (if any) to this AT since the last one (referenced by addrLastTxnTimestamp) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp)); - // If no transaction found, A will be zero. If A is zero, set addrResult to 1, otherwise 0. - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult)); - // If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction - codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckTradeTxn))); - // Stop and wait for next block - codeByteBuffer.put(OpCode.STP_IMD.compile()); - - /* Check transaction */ - labelCheckTradeTxn = codeByteBuffer.position(); - - // Update our 'last found transaction's timestamp' using 'timestamp' from transaction - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp)); - // Extract transaction type (message/payment) from transaction and save type in addrTxnType - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType)); - // If transaction type is not MESSAGE type then go look for another transaction - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelTradeTxnLoop))); - - /* Check transaction's sender. We're expecting AT creator's trade address for 'trade' message, or AT creator's own address for 'cancel' message. */ - - // Extract sender address from transaction into B register - codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B)); - // Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer)); - // Compare each part of message sender's address with AT creator's trade address. If they don't match, check for cancel situation. - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorTradeAddress1, calcOffset(codeByteBuffer, labelCheckCancelTxn))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelCheckCancelTxn))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelCheckCancelTxn))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelCheckCancelTxn))); - // Message sender's address matches AT creator's trade address so go process 'trade' message - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelCheckNonRefundTradeTxn == null ? 0 : labelCheckNonRefundTradeTxn)); - - /* Checking message sender for possible cancel message */ - labelCheckCancelTxn = codeByteBuffer.position(); - - // Compare each part of message sender's address with AT creator's address. If they don't match, look for another transaction. - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn))); - // Partner address is AT creator's address, so cancel offer and finish. - codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.CANCELLED.value)); - // We're finished forever (finishing auto-refunds remaining balance to AT creator) - codeByteBuffer.put(OpCode.FIN_IMD.compile()); - - /* Not trade nor cancel message */ - labelNotTradeNorCancelTxn = codeByteBuffer.position(); - - // Loop to find another transaction - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxnLoop == null ? 0 : labelTradeTxnLoop)); - - /* Possible switch-to-trade-mode message */ - labelCheckNonRefundTradeTxn = codeByteBuffer.position(); - - // Check 'trade' message we received has expected number of message bytes - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength)); - // If message length matches, branch to info extraction code - codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedTradeMessageLength, calcOffset(codeByteBuffer, labelTradeTxnExtract))); - // Message length didn't match - go back to finding another 'trade' MESSAGE transaction - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxnLoop == null ? 0 : labelTradeTxnLoop)); - - /* Extracting info from 'trade' MESSAGE transaction */ - labelTradeTxnExtract = codeByteBuffer.position(); - - // Extract message from transaction into B register - codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B)); - // Save B register into data segment starting at addrQortalPartnerAddress1 (as pointed to by addrQortalPartnerAddressPointer) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalPartnerAddressPointer)); - - // Extract trade partner's Litecoin public key hash (PKH) from message into B - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessagePartnerLitecoinPKHOffset)); - // Store partner's Litecoin PKH (we only really use values from B1-B3) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerLitecoinPKHPointer)); - // Extract AT trade timeout (minutes) (from B4) - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrRefundTimeout)); - - // Grab next 32 bytes - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessageHashOfSecretAOffset)); - - // Extract hash-of-secret-A (we only really use values from B1-B3) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrHashOfSecretAPointer)); - // Extract lockTime-A (from B4) - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrLockTimeA)); - - // Calculate trade timeout refund 'timestamp' by adding addrRefundTimeout minutes to this transaction's 'timestamp', then save into addrRefundTimestamp - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTxnTimestamp, addrRefundTimeout)); - - /* We are in 'trade mode' */ - codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.TRADING.value)); - - // Set restart position to after this opcode - codeByteBuffer.put(OpCode.SET_PCS.compile()); - - /* Loop, waiting for trade timeout or 'redeem' MESSAGE from Qortal trade partner */ - - // Fetch current block 'timestamp' - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp)); - // If we're not past refund 'timestamp' then look for next transaction - codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); - // We're past refund 'timestamp' so go refund everything back to AT creator - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund)); - - /* Transaction processing loop */ - labelRedeemTxnLoop = codeByteBuffer.position(); - - /* Sleep until message arrives */ - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.SLEEP_UNTIL_MESSAGE.value, addrLastTxnTimestamp)); - - // Find next transaction to this AT since the last one (if any) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp)); - // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult)); - // If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction - codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckRedeemTxn))); - // Stop and wait for next block - codeByteBuffer.put(OpCode.STP_IMD.compile()); - - /* Check transaction */ - labelCheckRedeemTxn = codeByteBuffer.position(); - - // Update our 'last found transaction's timestamp' using 'timestamp' from transaction - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp)); - // Extract transaction type (message/payment) from transaction and save type in addrTxnType - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType)); - // If transaction type is not MESSAGE type then go look for another transaction - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); - - /* Check message payload length */ - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength)); - // If message length matches, branch to sender checking code - codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedRedeemMessageLength, calcOffset(codeByteBuffer, labelCheckRedeemTxnSender))); - // Message length didn't match - go back to finding another 'redeem' MESSAGE transaction - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop)); - - /* Check transaction's sender */ - labelCheckRedeemTxnSender = codeByteBuffer.position(); - - // Extract sender address from transaction into B register - codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B)); - // Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer)); - // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction. - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalPartnerAddress1, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalPartnerAddress2, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalPartnerAddress3, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalPartnerAddress4, calcOffset(codeByteBuffer, labelRedeemTxnLoop))); - - /* Check 'secret-A' in transaction's message */ - - // Extract secret-A from first 32 bytes of message from transaction into B register - codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B)); - // Save B register into data segment starting at addrMessageData (as pointed to by addrMessageDataPointer) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageDataPointer)); - // Load B register with expected hash result (as pointed to by addrHashOfSecretAPointer) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrHashOfSecretAPointer)); - // Perform HASH160 using source data at addrMessageData. (Location and length specified via addrMessageDataPointer and addrMessageDataLength). - // Save the equality result (1 if they match, 0 otherwise) into addrResult. - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrResult, addrMessageDataPointer, addrMessageDataLength)); - // If hashes don't match, addrResult will be zero so go find another transaction - codeByteBuffer.put(OpCode.BNZ_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelPayout))); - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop)); - - /* Success! Pay arranged amount to receiving address */ - labelPayout = codeByteBuffer.position(); - - // Extract Qortal receiving address from next 32 bytes of message from transaction into B register - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageReceivingAddressOffset)); - // Save B register into data segment starting at addrPartnerReceivingAddress (as pointed to by addrPartnerReceivingAddressPointer) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerReceivingAddressPointer)); - // Pay AT's balance to receiving address - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrQortAmount)); - // Set redeemed mode - codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.REDEEMED.value)); - // We're finished forever (finishing auto-refunds remaining balance to AT creator) - codeByteBuffer.put(OpCode.FIN_IMD.compile()); - - // Fall-through to refunding any remaining balance back to AT creator - - /* Refund balance back to AT creator */ - labelRefund = codeByteBuffer.position(); - - // Set refunded mode - codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.REFUNDED.value)); - // We're finished forever (finishing auto-refunds remaining balance to AT creator) - codeByteBuffer.put(OpCode.FIN_IMD.compile()); - } catch (CompilationException e) { - throw new IllegalStateException("Unable to compile LTC-QORT ACCT?", e); - } - } - - codeByteBuffer.flip(); - - byte[] codeBytes = new byte[codeByteBuffer.limit()]; - codeByteBuffer.get(codeBytes); - - assert Arrays.equals(Crypto.digest(codeBytes), LitecoinACCTv2.CODE_BYTES_HASH) - : String.format("BTCACCT.CODE_BYTES_HASH mismatch: expected %s, actual %s", HashCode.fromBytes(CODE_BYTES_HASH), HashCode.fromBytes(Crypto.digest(codeBytes))); - - final short ciyamAtVersion = 2; - final short numCallStackPages = 0; - final short numUserStackPages = 0; - final long minActivationAmount = 0L; - - return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount); - } - - /** - * Returns CrossChainTradeData with useful info extracted from AT. - */ - @Override - public CrossChainTradeData populateTradeData(Repository repository, ATData atData) throws DataException { - ATStateData atStateData = repository.getATRepository().getLatestATState(atData.getATAddress()); - return populateTradeData(repository, atData.getCreatorPublicKey(), atData.getCreation(), atStateData); - } - - /** - * Returns CrossChainTradeData with useful info extracted from AT. - */ - @Override - public CrossChainTradeData populateTradeData(Repository repository, ATStateData atStateData) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atStateData.getATAddress()); - return populateTradeData(repository, atData.getCreatorPublicKey(), atData.getCreation(), atStateData); - } - - /** - * Returns CrossChainTradeData with useful info extracted from AT. - */ - public CrossChainTradeData populateTradeData(Repository repository, byte[] creatorPublicKey, long creationTimestamp, ATStateData atStateData) throws DataException { - byte[] addressBytes = new byte[25]; // for general use - String atAddress = atStateData.getATAddress(); - - CrossChainTradeData tradeData = new CrossChainTradeData(); - - tradeData.foreignBlockchain = SupportedBlockchain.LITECOIN.name(); - tradeData.acctName = NAME; - - tradeData.qortalAtAddress = atAddress; - tradeData.qortalCreator = Crypto.toAddress(creatorPublicKey); - tradeData.creationTimestamp = creationTimestamp; - - Account atAccount = new Account(repository, atAddress); - tradeData.qortBalance = atAccount.getConfirmedBalance(Asset.QORT); - - byte[] stateData = atStateData.getStateData(); - ByteBuffer dataByteBuffer = ByteBuffer.wrap(stateData); - dataByteBuffer.position(MachineState.HEADER_LENGTH); - - /* Constants */ - - // Skip creator's trade address - dataByteBuffer.get(addressBytes); - tradeData.qortalCreatorTradeAddress = Base58.encode(addressBytes); - dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length); - - // Creator's Litecoin/foreign public key hash - tradeData.creatorForeignPKH = new byte[20]; - dataByteBuffer.get(tradeData.creatorForeignPKH); - dataByteBuffer.position(dataByteBuffer.position() + 32 - tradeData.creatorForeignPKH.length); // skip to 32 bytes - - // We don't use secret-B - tradeData.hashOfSecretB = null; - - // Redeem payout - tradeData.qortAmount = dataByteBuffer.getLong(); - - // Expected LTC amount - tradeData.expectedForeignAmount = dataByteBuffer.getLong(); - - // Trade timeout - tradeData.tradeTimeout = (int) dataByteBuffer.getLong(); - - // Skip MESSAGE transaction type - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip expected 'trade' message length - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip expected 'redeem' message length - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip pointer to creator's address - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip pointer to partner's Qortal trade address - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip pointer to message sender - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip 'trade' message data offset for partner's Litecoin PKH - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip pointer to partner's Litecoin PKH - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip 'trade' message data offset for hash-of-secret-A - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip pointer to hash-of-secret-A - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip 'redeem' message data offset for partner's Qortal receiving address - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip pointer to message data - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip message data length - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip pointer to partner's receiving address - dataByteBuffer.position(dataByteBuffer.position() + 8); - - /* End of constants / begin variables */ - - // Skip AT creator's address - dataByteBuffer.position(dataByteBuffer.position() + 8 * 4); - - // Partner's trade address (if present) - dataByteBuffer.get(addressBytes); - String qortalRecipient = Base58.encode(addressBytes); - dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length); - - // Potential lockTimeA (if in trade mode) - int lockTimeA = (int) dataByteBuffer.getLong(); - - // AT refund timeout (probably only useful for debugging) - int refundTimeout = (int) dataByteBuffer.getLong(); - - // Trade-mode refund timestamp (AT 'timestamp' converted to Qortal block height) - long tradeRefundTimestamp = dataByteBuffer.getLong(); - - // Skip last transaction timestamp - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip block timestamp - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip transaction type - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip temporary result - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip temporary message sender - dataByteBuffer.position(dataByteBuffer.position() + 8 * 4); - - // Skip message length - dataByteBuffer.position(dataByteBuffer.position() + 8); - - // Skip temporary message data - dataByteBuffer.position(dataByteBuffer.position() + 8 * 4); - - // Potential hash160 of secret A - byte[] hashOfSecretA = new byte[20]; - dataByteBuffer.get(hashOfSecretA); - dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes - - // Potential partner's Litecoin PKH - byte[] partnerLitecoinPKH = new byte[20]; - dataByteBuffer.get(partnerLitecoinPKH); - dataByteBuffer.position(dataByteBuffer.position() + 32 - partnerLitecoinPKH.length); // skip to 32 bytes - - // Partner's receiving address (if present) - byte[] partnerReceivingAddress = new byte[25]; - dataByteBuffer.get(partnerReceivingAddress); - dataByteBuffer.position(dataByteBuffer.position() + 32 - partnerReceivingAddress.length); // skip to 32 bytes - - // Trade AT's 'mode' - long modeValue = dataByteBuffer.getLong(); - AcctMode mode = AcctMode.valueOf((int) (modeValue & 0xffL)); - - /* End of variables */ - - if (mode != null && mode != AcctMode.OFFERING) { - tradeData.mode = mode; - tradeData.refundTimeout = refundTimeout; - tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight; - tradeData.qortalPartnerAddress = qortalRecipient; - tradeData.hashOfSecretA = hashOfSecretA; - tradeData.partnerForeignPKH = partnerLitecoinPKH; - tradeData.lockTimeA = lockTimeA; - - if (mode == AcctMode.REDEEMED) - tradeData.qortalPartnerReceivingAddress = Base58.encode(partnerReceivingAddress); - } else { - tradeData.mode = AcctMode.OFFERING; - } - - tradeData.duplicateDeprecated(); - - return tradeData; - } - - /** Returns 'offer' MESSAGE payload for trade partner to send to AT creator's trade address. */ - public static byte[] buildOfferMessage(byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) { - byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA); - return Bytes.concat(partnerBitcoinPKH, hashOfSecretA, lockTimeABytes); - } - - /** Returns info extracted from 'offer' MESSAGE payload sent by trade partner to AT creator's trade address, or null if not valid. */ - public static OfferMessageData extractOfferMessageData(byte[] messageData) { - if (messageData == null || messageData.length != OFFER_MESSAGE_LENGTH) - return null; - - OfferMessageData offerMessageData = new OfferMessageData(); - offerMessageData.partnerLitecoinPKH = Arrays.copyOfRange(messageData, 0, 20); - offerMessageData.hashOfSecretA = Arrays.copyOfRange(messageData, 20, 40); - offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 40); - - return offerMessageData; - } - - /** Returns 'trade' MESSAGE payload for AT creator to send to AT. */ - public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { - byte[] data = new byte[TRADE_MESSAGE_LENGTH]; - byte[] partnerQortalAddressBytes = Base58.decode(partnerQortalTradeAddress); - byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA); - byte[] refundTimeoutBytes = BitTwiddling.toBEByteArray((long) refundTimeout); - - System.arraycopy(partnerQortalAddressBytes, 0, data, 0, partnerQortalAddressBytes.length); - System.arraycopy(partnerBitcoinPKH, 0, data, 32, partnerBitcoinPKH.length); - System.arraycopy(refundTimeoutBytes, 0, data, 56, refundTimeoutBytes.length); - System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length); - System.arraycopy(lockTimeABytes, 0, data, 88, lockTimeABytes.length); - - return data; - } - - /** Returns 'cancel' MESSAGE payload for AT creator to cancel trade AT. */ - @Override - public byte[] buildCancelMessage(String creatorQortalAddress) { - byte[] data = new byte[CANCEL_MESSAGE_LENGTH]; - byte[] creatorQortalAddressBytes = Base58.decode(creatorQortalAddress); - - System.arraycopy(creatorQortalAddressBytes, 0, data, 0, creatorQortalAddressBytes.length); - - return data; - } - - /** Returns 'redeem' MESSAGE payload for trade partner to send to AT. */ - public static byte[] buildRedeemMessage(byte[] secretA, String qortalReceivingAddress) { - byte[] data = new byte[REDEEM_MESSAGE_LENGTH]; - byte[] qortalReceivingAddressBytes = Base58.decode(qortalReceivingAddress); - - System.arraycopy(secretA, 0, data, 0, secretA.length); - System.arraycopy(qortalReceivingAddressBytes, 0, data, 32, qortalReceivingAddressBytes.length); - - return data; - } - - /** Returns refund timeout (minutes) based on trade partner's 'offer' MESSAGE timestamp and P2SH-A locktime. */ - public static int calcRefundTimeout(long offerMessageTimestamp, int lockTimeA) { - // refund should be triggered halfway between offerMessageTimestamp and lockTimeA - return (int) ((lockTimeA - (offerMessageTimestamp / 1000L)) / 2L / 60L); - } - - @Override - public byte[] findSecretA(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException { - String atAddress = crossChainTradeData.qortalAtAddress; - String redeemerAddress = crossChainTradeData.qortalPartnerAddress; - - // We don't have partner's public key so we check every message to AT - List messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, atAddress, null, null, null); - if (messageTransactionsData == null) - return null; - - // Find 'redeem' message - for (MessageTransactionData messageTransactionData : messageTransactionsData) { - // Check message payload type/encryption - if (messageTransactionData.isText() || messageTransactionData.isEncrypted()) - continue; - - // Check message payload size - byte[] messageData = messageTransactionData.getData(); - if (messageData.length != REDEEM_MESSAGE_LENGTH) - // Wrong payload length - continue; - - // Check sender - if (!Crypto.toAddress(messageTransactionData.getSenderPublicKey()).equals(redeemerAddress)) - // Wrong sender; - continue; - - // Extract secretA - byte[] secretA = new byte[32]; - System.arraycopy(messageData, 0, secretA, 0, secretA.length); - - byte[] hashOfSecretA = Crypto.hash160(secretA); - if (!Arrays.equals(hashOfSecretA, crossChainTradeData.hashOfSecretA)) - continue; - - return secretA; - } - - return null; - } - -} diff --git a/src/main/java/org/qortal/crosschain/PirateChain.java b/src/main/java/org/qortal/crosschain/PirateChain.java new file mode 100644 index 00000000..09b37481 --- /dev/null +++ b/src/main/java/org/qortal/crosschain/PirateChain.java @@ -0,0 +1,659 @@ +package org.qortal.crosschain; + +import cash.z.wallet.sdk.rpc.CompactFormats; +import com.google.common.hash.HashCode; +import com.rust.litewalletjni.LiteWalletJni; +import org.bitcoinj.core.*; +import org.bitcoinj.crypto.ChildNumber; +import org.bitcoinj.crypto.DeterministicKey; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptBuilder; +import org.bitcoinj.wallet.DeterministicKeyChain; +import org.bitcoinj.wallet.Wallet; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.libdohj.params.LitecoinRegTestParams; +import org.libdohj.params.LitecoinTestNet3Params; +import org.libdohj.params.PirateChainMainNetParams; +import org.qortal.api.model.crosschain.PirateChainSendRequest; +import org.qortal.controller.PirateChainWalletController; +import org.qortal.crosschain.PirateLightClient.Server; +import org.qortal.crosschain.PirateLightClient.Server.ConnectionType; +import org.qortal.crypto.Crypto; +import org.qortal.settings.Settings; +import org.qortal.transform.TransformationException; +import org.qortal.utils.BitTwiddling; + +import java.nio.ByteBuffer; +import java.util.*; + +public class PirateChain extends Bitcoiny { + + public static final String CURRENCY_CODE = "ARRR"; + + private static final Coin DEFAULT_FEE_PER_KB = Coin.valueOf(10000); // 0.0001 ARRR per 1000 bytes + + private static final long MINIMUM_ORDER_AMOUNT = 10000; // 0.0001 ARRR minimum order, to avoid dust errors // TODO: increase this + + // Temporary values until a dynamic fee system is written. + private static final long MAINNET_FEE = 10000L; // 0.0001 ARRR + private static final long NON_MAINNET_FEE = 10000L; // 0.0001 ARRR + + private static final Map DEFAULT_LITEWALLET_PORTS = new EnumMap<>(ConnectionType.class); + static { + DEFAULT_LITEWALLET_PORTS.put(ConnectionType.TCP, 9067); + DEFAULT_LITEWALLET_PORTS.put(ConnectionType.SSL, 443); + } + + public enum PirateChainNet { + MAIN { + @Override + public NetworkParameters getParams() { + return PirateChainMainNetParams.get(); + } + + @Override + public Collection getServers() { + return Arrays.asList( + // Servers chosen on NO BASIS WHATSOEVER from various sources! + new Server("arrrlightd.qortal.online", ConnectionType.SSL, 443), + new Server("arrrlightd1.qortal.online", ConnectionType.SSL, 443), + new Server("arrrlightd2.qortal.online", ConnectionType.SSL, 443), + new Server("lightd.pirate.black", ConnectionType.SSL, 443)); + } + + @Override + public String getGenesisHash() { + return "027e3758c3a65b12aa1046462b486d0a63bfa1beae327897f56c5cfb7daaae71"; + } + + @Override + public long getP2shFee(Long timestamp) { + // TODO: This will need to be replaced with something better in the near future! + return MAINNET_FEE; + } + }, + TEST3 { + @Override + public NetworkParameters getParams() { + return LitecoinTestNet3Params.get(); + } + + @Override + public Collection getServers() { + return Arrays.asList(); + } + + @Override + public String getGenesisHash() { + return "4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0"; + } + + @Override + public long getP2shFee(Long timestamp) { + return NON_MAINNET_FEE; + } + }, + REGTEST { + @Override + public NetworkParameters getParams() { + return LitecoinRegTestParams.get(); + } + + @Override + public Collection getServers() { + return Arrays.asList( + new Server("localhost", ConnectionType.TCP, 9067), + new Server("localhost", ConnectionType.SSL, 443)); + } + + @Override + public String getGenesisHash() { + // This is unique to each regtest instance + return null; + } + + @Override + public long getP2shFee(Long timestamp) { + return NON_MAINNET_FEE; + } + }; + + public abstract NetworkParameters getParams(); + public abstract Collection getServers(); + public abstract String getGenesisHash(); + public abstract long getP2shFee(Long timestamp) throws ForeignBlockchainException; + } + + private static PirateChain instance; + + private final PirateChainNet pirateChainNet; + + // Constructors and instance + + private PirateChain(PirateChainNet pirateChainNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) { + super(blockchain, bitcoinjContext, currencyCode); + this.pirateChainNet = pirateChainNet; + + LOGGER.info(() -> String.format("Starting Pirate Chain support using %s", this.pirateChainNet.name())); + } + + public static synchronized PirateChain getInstance() { + if (instance == null) { + PirateChainNet pirateChainNet = Settings.getInstance().getPirateChainNet(); + + BitcoinyBlockchainProvider pirateLightClient = new PirateLightClient("PirateChain-" + pirateChainNet.name(), pirateChainNet.getGenesisHash(), pirateChainNet.getServers(), DEFAULT_LITEWALLET_PORTS); + Context bitcoinjContext = new Context(pirateChainNet.getParams()); + + instance = new PirateChain(pirateChainNet, pirateLightClient, bitcoinjContext, CURRENCY_CODE); + + pirateLightClient.setBlockchain(instance); + } + + return instance; + } + + // Getters & setters + + public static synchronized void resetForTesting() { + instance = null; + } + + // Actual useful methods for use by other classes + + /** Default Litecoin fee is lower than Bitcoin: only 10sats/byte. */ + @Override + public Coin getFeePerKb() { + return DEFAULT_FEE_PER_KB; + } + + @Override + public long getMinimumOrderAmount() { + return MINIMUM_ORDER_AMOUNT; + } + + /** + * Returns estimated LTC fee, in sats per 1000bytes, optionally for historic timestamp. + * + * @param timestamp optional milliseconds since epoch, or null for 'now' + * @return sats per 1000bytes, or throws ForeignBlockchainException if something went wrong + */ + @Override + public long getP2shFee(Long timestamp) throws ForeignBlockchainException { + return this.pirateChainNet.getP2shFee(timestamp); + } + + /** + * Returns confirmed balance, based on passed payment script. + *

      + * @return confirmed balance, or zero if balance unknown + * @throws ForeignBlockchainException if there was an error + */ + public long getConfirmedBalance(String base58Address) throws ForeignBlockchainException { + return this.blockchainProvider.getConfirmedAddressBalance(base58Address); + } + + /** + * Returns median timestamp from latest 11 blocks, in seconds. + *

      + * @throws ForeignBlockchainException if error occurs + */ + @Override + public int getMedianBlockTime() throws ForeignBlockchainException { + int height = this.blockchainProvider.getCurrentHeight(); + + // Grab latest 11 blocks + List blockTimestamps = this.blockchainProvider.getBlockTimestamps(height - 11, 11); + if (blockTimestamps.size() < 11) + throw new ForeignBlockchainException("Not enough blocks to determine median block time"); + + // Descending order + blockTimestamps.sort((a, b) -> Long.compare(b, a)); + + // Pick median + return Math.toIntExact(blockTimestamps.get(5)); + } + + /** + * Returns list of compact blocks + *

      + * @throws ForeignBlockchainException if error occurs + */ + public List getCompactBlocks(int startHeight, int count) throws ForeignBlockchainException { + return this.blockchainProvider.getCompactBlocks(startHeight, count); + } + + + @Override + public boolean isValidAddress(String address) { + // Start with some simple checks + if (address == null || !address.toLowerCase().startsWith("zs") || address.length() != 78) { + return false; + } + + // Now try Bech32 decoding the address (which includes checksum verification) + try { + Bech32.Bech32Data decoded = Bech32.decode(address); + return (decoded != null && Objects.equals("zs", decoded.hrp)); + } + catch (AddressFormatException e) { + // Invalid address, checksum failed, etc + return false; + } + } + + @Override + public boolean isValidWalletKey(String walletKey) { + // For Pirate Chain, we only care that the key is a random string + // 32 characters in length, as it is used as entropy for the seed. + return walletKey != null && Base58.decode(walletKey).length == 32; + } + + /** Returns 't3' prefixed P2SH address using passed redeem script. */ + public String deriveP2shAddress(byte[] redeemScriptBytes) { + Context.propagate(bitcoinjContext); + byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes); + return LegacyZcashAddress.fromScriptHash(this.params, redeemScriptHash).toString(); + } + + /** Returns 'b' prefixed P2SH address using passed redeem script. */ + public String deriveP2shAddressBPrefix(byte[] redeemScriptBytes) { + Context.propagate(bitcoinjContext); + byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes); + return LegacyAddress.fromScriptHash(this.params, redeemScriptHash).toString(); + } + + public Long getWalletBalance(String entropy58) throws ForeignBlockchainException { + synchronized (this) { + PirateChainWalletController walletController = PirateChainWalletController.getInstance(); + walletController.initWithEntropy58(entropy58); + walletController.ensureInitialized(); + walletController.ensureSynchronized(); + walletController.ensureNotNullSeed(); + + // Get balance + String response = LiteWalletJni.execute("balance", ""); + JSONObject json = new JSONObject(response); + if (json.has("zbalance")) { + return json.getLong("zbalance"); + } + + throw new ForeignBlockchainException("Unable to determine balance"); + } + } + + public List getWalletTransactions(String entropy58) throws ForeignBlockchainException { + synchronized (this) { + PirateChainWalletController walletController = PirateChainWalletController.getInstance(); + walletController.initWithEntropy58(entropy58); + walletController.ensureInitialized(); + walletController.ensureSynchronized(); + walletController.ensureNotNullSeed(); + + List transactions = new ArrayList<>(); + + // Get transactions list + String response = LiteWalletJni.execute("list", ""); + JSONArray transactionsJson = new JSONArray(response); + if (transactionsJson != null) { + for (int i = 0; i < transactionsJson.length(); i++) { + JSONObject transactionJson = transactionsJson.getJSONObject(i); + + if (transactionJson.has("txid")) { + String txId = transactionJson.getString("txid"); + Long timestamp = transactionJson.getLong("datetime"); + Long amount = transactionJson.getLong("amount"); + Long fee = transactionJson.getLong("fee"); + String memo = null; + + if (transactionJson.has("incoming_metadata")) { + JSONArray incomingMetadatas = transactionJson.getJSONArray("incoming_metadata"); + if (incomingMetadatas != null) { + for (int j = 0; j < incomingMetadatas.length(); j++) { + JSONObject incomingMetadata = incomingMetadatas.getJSONObject(j); + if (incomingMetadata.has("value")) { + //String address = incomingMetadata.getString("address"); + Long value = incomingMetadata.getLong("value"); + amount = value; // TODO: figure out how to parse transactions with multiple incomingMetadata entries + } + + if (incomingMetadata.has("memo") && !incomingMetadata.isNull("memo")) { + memo = incomingMetadata.getString("memo"); + } + } + } + } + + if (transactionJson.has("outgoing_metadata")) { + JSONArray outgoingMetadatas = transactionJson.getJSONArray("outgoing_metadata"); + for (int j = 0; j < outgoingMetadatas.length(); j++) { + JSONObject outgoingMetadata = outgoingMetadatas.getJSONObject(j); + + if (outgoingMetadata.has("memo") && !outgoingMetadata.isNull("memo")) { + memo = outgoingMetadata.getString("memo"); + } + } + } + + long timestampMillis = Math.toIntExact(timestamp) * 1000L; + SimpleTransaction transaction = new SimpleTransaction(txId, timestampMillis, amount, fee, null, null, memo); + transactions.add(transaction); + } + } + } + + return transactions; + } + } + + public String getWalletAddress(String entropy58) throws ForeignBlockchainException { + synchronized (this) { + PirateChainWalletController walletController = PirateChainWalletController.getInstance(); + walletController.initWithEntropy58(entropy58); + walletController.ensureInitialized(); + walletController.ensureNotNullSeed(); + + return walletController.getCurrentWallet().getWalletAddress(); + } + } + + public String getUnusedReceiveAddress(String key58) throws ForeignBlockchainException { + // For now, return the main wallet address + // FUTURE: generate an unused one + return this.getWalletAddress(key58); + } + + public String sendCoins(PirateChainSendRequest pirateChainSendRequest) throws ForeignBlockchainException { + PirateChainWalletController walletController = PirateChainWalletController.getInstance(); + walletController.initWithEntropy58(pirateChainSendRequest.entropy58); + walletController.ensureInitialized(); + walletController.ensureSynchronized(); + walletController.ensureNotNullSeed(); + + // Unlock wallet + walletController.getCurrentWallet().unlock(); + + // Build spend + JSONObject txn = new JSONObject(); + txn.put("input", walletController.getCurrentWallet().getWalletAddress()); + txn.put("fee", MAINNET_FEE); + + JSONObject output = new JSONObject(); + output.put("address", pirateChainSendRequest.receivingAddress); + output.put("amount", pirateChainSendRequest.arrrAmount); + output.put("memo", pirateChainSendRequest.memo); + + JSONArray outputs = new JSONArray(); + outputs.put(output); + txn.put("output", outputs); + + String txnString = txn.toString(); + + // Send the coins + String response = LiteWalletJni.execute("send", txnString); + JSONObject json = new JSONObject(response); + try { + if (json.has("txid")) { // Success + return json.getString("txid"); + } + else if (json.has("error")) { + String error = json.getString("error"); + throw new ForeignBlockchainException(error); + } + + } catch (JSONException e) { + throw new ForeignBlockchainException(e.getMessage()); + } + + throw new ForeignBlockchainException("Something went wrong"); + } + + public String fundP2SH(String entropy58, String receivingAddress, long amount, + String redeemScript58) throws ForeignBlockchainException { + + PirateChainWalletController walletController = PirateChainWalletController.getInstance(); + walletController.initWithEntropy58(entropy58); + walletController.ensureInitialized(); + walletController.ensureSynchronized(); + walletController.ensureNotNullSeed(); + + // Unlock wallet + walletController.getCurrentWallet().unlock(); + + // Build spend + JSONObject txn = new JSONObject(); + txn.put("input", walletController.getCurrentWallet().getWalletAddress()); + txn.put("fee", MAINNET_FEE); + + JSONObject output = new JSONObject(); + output.put("address", receivingAddress); + output.put("amount", amount); + //output.put("memo", memo); + + JSONArray outputs = new JSONArray(); + outputs.put(output); + txn.put("output", outputs); + txn.put("script", redeemScript58); + + String txnString = txn.toString(); + + // Send the coins + String response = LiteWalletJni.execute("sendp2sh", txnString); + JSONObject json = new JSONObject(response); + try { + if (json.has("txid")) { // Success + return json.getString("txid"); + } + else if (json.has("error")) { + String error = json.getString("error"); + throw new ForeignBlockchainException(error); + } + + } catch (JSONException e) { + throw new ForeignBlockchainException(e.getMessage()); + } + + throw new ForeignBlockchainException("Something went wrong"); + } + + public String redeemP2sh(String p2shAddress, String receivingAddress, long amount, String redeemScript58, + String fundingTxid58, String secret58, String privateKey58) throws ForeignBlockchainException { + + // Use null seed wallet since we may not have the entropy bytes for a real wallet's seed + PirateChainWalletController walletController = PirateChainWalletController.getInstance(); + walletController.initNullSeedWallet(); + walletController.ensureInitialized(); + + walletController.getCurrentWallet().unlock(); + + // Build spend + JSONObject txn = new JSONObject(); + txn.put("input", p2shAddress); + txn.put("fee", MAINNET_FEE); + + JSONObject output = new JSONObject(); + output.put("address", receivingAddress); + output.put("amount", amount); + // output.put("memo", ""); // Maybe useful in future to include trade details? + + JSONArray outputs = new JSONArray(); + outputs.put(output); + txn.put("output", outputs); + + txn.put("script", redeemScript58); + txn.put("txid", fundingTxid58); + txn.put("locktime", 0); // Must be 0 when redeeming + txn.put("secret", secret58); + txn.put("privkey", privateKey58); + + String txnString = txn.toString(); + + // Redeem the P2SH + String response = LiteWalletJni.execute("redeemp2sh", txnString); + JSONObject json = new JSONObject(response); + try { + if (json.has("txid")) { // Success + return json.getString("txid"); + } + else if (json.has("error")) { + String error = json.getString("error"); + throw new ForeignBlockchainException(error); + } + + } catch (JSONException e) { + throw new ForeignBlockchainException(e.getMessage()); + } + + throw new ForeignBlockchainException("Something went wrong"); + } + + public String refundP2sh(String p2shAddress, String receivingAddress, long amount, String redeemScript58, + String fundingTxid58, int lockTime, String privateKey58) throws ForeignBlockchainException { + + // Use null seed wallet since we may not have the entropy bytes for a real wallet's seed + PirateChainWalletController walletController = PirateChainWalletController.getInstance(); + walletController.initNullSeedWallet(); + walletController.ensureInitialized(); + + walletController.getCurrentWallet().unlock(); + + // Build spend + JSONObject txn = new JSONObject(); + txn.put("input", p2shAddress); + txn.put("fee", MAINNET_FEE); + + JSONObject output = new JSONObject(); + output.put("address", receivingAddress); + output.put("amount", amount); + // output.put("memo", ""); // Maybe useful in future to include trade details? + + JSONArray outputs = new JSONArray(); + outputs.put(output); + txn.put("output", outputs); + + txn.put("script", redeemScript58); + txn.put("txid", fundingTxid58); + txn.put("locktime", lockTime); + txn.put("secret", ""); // Must be blank when refunding + txn.put("privkey", privateKey58); + + String txnString = txn.toString(); + + // Redeem the P2SH + String response = LiteWalletJni.execute("redeemp2sh", txnString); + JSONObject json = new JSONObject(response); + try { + if (json.has("txid")) { // Success + return json.getString("txid"); + } + else if (json.has("error")) { + String error = json.getString("error"); + throw new ForeignBlockchainException(error); + } + + } catch (JSONException e) { + throw new ForeignBlockchainException(e.getMessage()); + } + + throw new ForeignBlockchainException("Something went wrong"); + } + + public String getSyncStatus(String entropy58) throws ForeignBlockchainException { + synchronized (this) { + PirateChainWalletController walletController = PirateChainWalletController.getInstance(); + walletController.initWithEntropy58(entropy58); + + return walletController.getSyncStatus(); + } + } + + public static BitcoinyTransaction deserializeRawTransaction(String rawTransactionHex) throws TransformationException { + byte[] rawTransactionData = HashCode.fromString(rawTransactionHex).asBytes(); + ByteBuffer byteBuffer = ByteBuffer.wrap(rawTransactionData); + + // Header + int header = BitTwiddling.readU32(byteBuffer); + boolean overwintered = ((header >> 31 & 0xff) == 255); + int version = header & 0x7FFFFFFF; + + // Version group ID + int versionGroupId = 0; + if (overwintered) { + versionGroupId = BitTwiddling.readU32(byteBuffer); + } + + boolean isOverwinterV3 = overwintered && versionGroupId == 0x03C48270 && version == 3; + boolean isSaplingV4 = overwintered && versionGroupId == 0x892F2085 && version == 4; + if (overwintered && !(isOverwinterV3 || isSaplingV4)) { + throw new TransformationException("Unknown transaction format"); + } + + // Inputs + List inputs = new ArrayList<>(); + int vinCount = BitTwiddling.readU8(byteBuffer); + for (int i=0; i outputs = new ArrayList<>(); + int voutCount = BitTwiddling.readU8(byteBuffer); + for (int i=0; i *

        - *
      • Bob generates Dogecoin & Qortal 'trade' keys + *
      • Bob generates PirateChain & Qortal 'trade' keys *
          *
        • private key required to sign P2SH redeem tx
        • *
        • private key could be used to create 'secret' (e.g. double-SHA256)
        • @@ -42,12 +40,12 @@ import static org.ciyam.at.OpCode.calcOffset; * *
        • Alice finds Qortal AT and wants to trade *
            - *
          • Alice generates Dogecoin & Qortal 'trade' keys
          • - *
          • Alice funds Dogecoin P2SH-A
          • + *
          • Alice generates PirateChain & Qortal 'trade' keys
          • + *
          • Alice funds PirateChain P2SH-A
          • *
          • Alice sends 'offer' MESSAGE to Bob from her Qortal trade address, containing: *
              *
            • hash-of-secret-A
            • - *
            • her 'trade' Dogecoin PKH
            • + *
            • her 'trade' Pirate Chain public key
            • *
            *
          • *
          @@ -58,7 +56,7 @@ import static org.ciyam.at.OpCode.calcOffset; *
        • Sends 'trade' MESSAGE to Qortal AT from his trade address, containing: *
            *
          • Alice's trade Qortal address
          • - *
          • Alice's trade Dogecoin PKH
          • + *
          • Alice's trade Pirate Chain public key
          • *
          • hash-of-secret-A
          • *
          *
        • @@ -77,48 +75,46 @@ import static org.ciyam.at.OpCode.calcOffset; * *
        • Bob checks AT, extracts secret-A *
            - *
          • Bob redeems P2SH-A using his Dogecoin trade key and secret-A
          • - *
          • P2SH-A DOGE funds end up at Dogecoin address determined by redeem transaction output(s)
          • + *
          • Bob redeems P2SH-A using his PirateChain trade key and secret-A
          • + *
          • P2SH-A ARRR funds end up at PirateChain address determined by redeem transaction output(s)
          • *
          *
        • *
        */ -public class DogecoinACCTv2 implements ACCT { +public class PirateChainACCTv3 implements ACCT { - private static final Logger LOGGER = LogManager.getLogger(DogecoinACCTv2.class); - - public static final String NAME = DogecoinACCTv2.class.getSimpleName(); - public static final byte[] CODE_BYTES_HASH = HashCode.fromString("6fff38d6eeb06568a9c879c5628527730319844aa0de53f5f4ffab5506efe885").asBytes(); // SHA256 of AT code bytes + public static final String NAME = PirateChainACCTv3.class.getSimpleName(); + public static final byte[] CODE_BYTES_HASH = HashCode.fromString("fc2818ac0819ab658a065ab0d050e75f167921e2dce5969b9b7741e47e477d83").asBytes(); // SHA256 of AT code bytes public static final int SECRET_LENGTH = 32; /** Value offset into AT segment where 'mode' variable (long) is stored. (Multiply by MachineState.VALUE_SIZE for byte offset). */ - private static final int MODE_VALUE_OFFSET = 61; + private static final int MODE_VALUE_OFFSET = 68; /** Byte offset into AT state data where 'mode' variable (long) is stored. */ public static final int MODE_BYTE_OFFSET = MachineState.HEADER_LENGTH + (MODE_VALUE_OFFSET * MachineState.VALUE_SIZE); public static class OfferMessageData { - public byte[] partnerDogecoinPKH; + public byte[] partnerPirateChainPublicKey; public byte[] hashOfSecretA; public long lockTimeA; } - public static final int OFFER_MESSAGE_LENGTH = 20 /*partnerDogecoinPKH*/ + 20 /*hashOfSecretA*/ + 8 /*lockTimeA*/; + public static final int OFFER_MESSAGE_LENGTH = 33 /*partnerPirateChainPublicKey*/ + 20 /*hashOfSecretA*/ + 8 /*lockTimeA*/; public static final int TRADE_MESSAGE_LENGTH = 32 /*partner's Qortal trade address (padded from 25 to 32)*/ - + 24 /*partner's Dogecoin PKH (padded from 20 to 24)*/ + + 40 /*partner's Pirate Chain public key (padded from 33 to 40)*/ + 8 /*AT trade timeout (minutes)*/ + 24 /*hash of secret-A (padded from 20 to 24)*/ + 8 /*lockTimeA*/; public static final int REDEEM_MESSAGE_LENGTH = 32 /*secret-A*/ + 32 /*partner's Qortal receiving address padded from 25 to 32*/; public static final int CANCEL_MESSAGE_LENGTH = 32 /*AT creator's Qortal address*/; - private static DogecoinACCTv2 instance; + private static PirateChainACCTv3 instance; - private DogecoinACCTv2() { + private PirateChainACCTv3() { } - public static synchronized DogecoinACCTv2 getInstance() { + public static synchronized PirateChainACCTv3 getInstance() { if (instance == null) - instance = new DogecoinACCTv2(); + instance = new PirateChainACCTv3(); return instance; } @@ -135,7 +131,7 @@ public class DogecoinACCTv2 implements ACCT { @Override public ForeignBlockchain getBlockchain() { - return Dogecoin.getInstance(); + return PirateChain.getInstance(); } /** @@ -145,14 +141,14 @@ public class DogecoinACCTv2 implements ACCT { * 32-byte secret to the AT, before the AT automatically refunds the AT's creator. * * @param creatorTradeAddress AT creator's trade Qortal address - * @param dogecoinPublicKeyHash 20-byte HASH160 of creator's trade Dogecoin public key + * @param pirateChainPublicKeyHash 33-byte creator's trade PirateChain public key * @param qortAmount how much QORT to pay trade partner if they send correct 32-byte secrets to AT - * @param dogecoinAmount how much DOGE the AT creator is expecting to trade + * @param arrrAmount how much ARRR the AT creator is expecting to trade * @param tradeTimeout suggested timeout for entire trade */ - public static byte[] buildQortalAT(String creatorTradeAddress, byte[] dogecoinPublicKeyHash, long qortAmount, long dogecoinAmount, int tradeTimeout) { - if (dogecoinPublicKeyHash.length != 20) - throw new IllegalArgumentException("Dogecoin public key hash should be 20 bytes"); + public static byte[] buildQortalAT(String creatorTradeAddress, byte[] pirateChainPublicKeyHash, long qortAmount, long arrrAmount, int tradeTimeout) { + if (pirateChainPublicKeyHash.length != 33) + throw new IllegalArgumentException("PirateChain public key hash should be 33 bytes"); // Labels for data segment addresses int addrCounter = 0; @@ -164,11 +160,11 @@ public class DogecoinACCTv2 implements ACCT { final int addrCreatorTradeAddress3 = addrCounter++; final int addrCreatorTradeAddress4 = addrCounter++; - final int addrDogecoinPublicKeyHash = addrCounter; - addrCounter += 4; + final int addrPirateChainPublicKeyHash = addrCounter; + addrCounter += 5; final int addrQortAmount = addrCounter++; - final int addrDogecoinAmount = addrCounter++; + final int addrarrrAmount = addrCounter++; final int addrTradeTimeout = addrCounter++; final int addrMessageTxnType = addrCounter++; @@ -179,8 +175,10 @@ public class DogecoinACCTv2 implements ACCT { final int addrQortalPartnerAddressPointer = addrCounter++; final int addrMessageSenderPointer = addrCounter++; - final int addrTradeMessagePartnerDogecoinPKHOffset = addrCounter++; - final int addrPartnerDogecoinPKHPointer = addrCounter++; + final int addrTradeMessagePartnerPirateChainPublicKeyFirst32BytesOffset = addrCounter++; + final int addrTradeMessagePartnerPirateChainPublicKeyLastByteOffset = addrCounter++; // Remainder of public key, plus timeout + final int addrPartnerPirateChainPublicKeyFirst32BytesPointer = addrCounter++; + final int addrPartnerPirateChainPublicKeyLastBytePointer = addrCounter++; // Remainder of public key final int addrTradeMessageHashOfSecretAOffset = addrCounter++; final int addrHashOfSecretAPointer = addrCounter++; @@ -226,9 +224,12 @@ public class DogecoinACCTv2 implements ACCT { final int addrHashOfSecretA = addrCounter; addrCounter += 4; - final int addrPartnerDogecoinPKH = addrCounter; + final int addrPartnerPirateChainPublicKeyFirst32Bytes = addrCounter; addrCounter += 4; + final int addrPartnerPirateChainPublicKeyLastByte = addrCounter; + addrCounter += 4; // We retrieve using GET_B_IND, so need to allow space for the full 32 bytes + final int addrPartnerReceivingAddress = addrCounter; addrCounter += 4; @@ -243,17 +244,17 @@ public class DogecoinACCTv2 implements ACCT { byte[] creatorTradeAddressBytes = Base58.decode(creatorTradeAddress); dataByteBuffer.put(Bytes.ensureCapacity(creatorTradeAddressBytes, 32, 0)); - // Dogecoin public key hash - assert dataByteBuffer.position() == addrDogecoinPublicKeyHash * MachineState.VALUE_SIZE : "addrDogecoinPublicKeyHash incorrect"; - dataByteBuffer.put(Bytes.ensureCapacity(dogecoinPublicKeyHash, 32, 0)); + // PirateChain public key hash + assert dataByteBuffer.position() == addrPirateChainPublicKeyHash * MachineState.VALUE_SIZE : "addrPirateChainPublicKeyHash incorrect"; + dataByteBuffer.put(Bytes.ensureCapacity(pirateChainPublicKeyHash, 40, 0)); // Redeem Qort amount assert dataByteBuffer.position() == addrQortAmount * MachineState.VALUE_SIZE : "addrQortAmount incorrect"; dataByteBuffer.putLong(qortAmount); - // Expected Dogecoin amount - assert dataByteBuffer.position() == addrDogecoinAmount * MachineState.VALUE_SIZE : "addrDogecoinAmount incorrect"; - dataByteBuffer.putLong(dogecoinAmount); + // Expected PirateChain amount + assert dataByteBuffer.position() == addrarrrAmount * MachineState.VALUE_SIZE : "addrarrrAmount incorrect"; + dataByteBuffer.putLong(arrrAmount); // Suggested trade timeout (minutes) assert dataByteBuffer.position() == addrTradeTimeout * MachineState.VALUE_SIZE : "addrTradeTimeout incorrect"; @@ -283,17 +284,25 @@ public class DogecoinACCTv2 implements ACCT { assert dataByteBuffer.position() == addrMessageSenderPointer * MachineState.VALUE_SIZE : "addrMessageSenderPointer incorrect"; dataByteBuffer.putLong(addrMessageSender1); - // Offset into 'trade' MESSAGE data payload for extracting partner's Dogecoin PKH - assert dataByteBuffer.position() == addrTradeMessagePartnerDogecoinPKHOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerDogecoinPKHOffset incorrect"; + // Offset into 'trade' MESSAGE data payload for extracting first 32 bytes of partner's Pirate Chain public key + assert dataByteBuffer.position() == addrTradeMessagePartnerPirateChainPublicKeyFirst32BytesOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerPirateChainPublicKeyFirst32BytesOffset incorrect"; dataByteBuffer.putLong(32L); - // Index into data segment of partner's Dogecoin PKH, used by GET_B_IND - assert dataByteBuffer.position() == addrPartnerDogecoinPKHPointer * MachineState.VALUE_SIZE : "addrPartnerDogecoinPKHPointer incorrect"; - dataByteBuffer.putLong(addrPartnerDogecoinPKH); + // Offset into 'trade' MESSAGE data payload for extracting last byte of public key + assert dataByteBuffer.position() == addrTradeMessagePartnerPirateChainPublicKeyLastByteOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerPirateChainPublicKeyLastByteOffset incorrect"; + dataByteBuffer.putLong(64L); + + // Index into data segment of partner's Pirate Chain public key, used by GET_B_IND + assert dataByteBuffer.position() == addrPartnerPirateChainPublicKeyFirst32BytesPointer * MachineState.VALUE_SIZE : "addrPartnerPirateChainPublicKeyFirst32BytesPointer incorrect"; + dataByteBuffer.putLong(addrPartnerPirateChainPublicKeyFirst32Bytes); + + // Index into data segment of remainder of partner's Pirate Chain public key, used by GET_B_IND + assert dataByteBuffer.position() == addrPartnerPirateChainPublicKeyLastBytePointer * MachineState.VALUE_SIZE : "addrPartnerPirateChainPublicKeyLastBytePointer incorrect"; + dataByteBuffer.putLong(addrPartnerPirateChainPublicKeyLastByte); // Offset into 'trade' MESSAGE data payload for extracting hash-of-secret-A assert dataByteBuffer.position() == addrTradeMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrTradeMessageHashOfSecretAOffset incorrect"; - dataByteBuffer.putLong(64L); + dataByteBuffer.putLong(80L); // Index into data segment to hash of secret A, used by GET_B_IND assert dataByteBuffer.position() == addrHashOfSecretAPointer * MachineState.VALUE_SIZE : "addrHashOfSecretAPointer incorrect"; @@ -338,9 +347,6 @@ public class DogecoinACCTv2 implements ACCT { try { /* Initialization */ - /* NOP - to ensure DOGECOIN ACCT is unique */ - codeByteBuffer.put(OpCode.NOP.compile()); - // Use AT creation 'timestamp' as starting point for finding transactions sent to AT codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxnTimestamp)); @@ -429,12 +435,17 @@ public class DogecoinACCTv2 implements ACCT { // Save B register into data segment starting at addrQortalPartnerAddress1 (as pointed to by addrQortalPartnerAddressPointer) codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalPartnerAddressPointer)); - // Extract trade partner's Dogecoin public key hash (PKH) from message into B - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessagePartnerDogecoinPKHOffset)); - // Store partner's Dogecoin PKH (we only really use values from B1-B3) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerDogecoinPKHPointer)); - // Extract AT trade timeout (minutes) (from B4) - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrRefundTimeout)); + // Extract first 32 bytes of trade partner's Pirate Chain public key from message into B + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessagePartnerPirateChainPublicKeyFirst32BytesOffset)); + // Store first 32 bytes of partner's Pirate Chain public key + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerPirateChainPublicKeyFirst32BytesPointer)); + + // Extract last byte of public key, plus trade timeout, from message into B + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessagePartnerPirateChainPublicKeyLastByteOffset)); + // Store last byte of partner's Pirate Chain public key + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerPirateChainPublicKeyLastBytePointer)); + // Extract AT trade timeout (minutes) (from B2) + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B2, addrRefundTimeout)); // Grab next 32 bytes codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessageHashOfSecretAOffset)); @@ -465,9 +476,6 @@ public class DogecoinACCTv2 implements ACCT { /* Transaction processing loop */ labelRedeemTxnLoop = codeByteBuffer.position(); - /* Sleep until message arrives */ - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.SLEEP_UNTIL_MESSAGE.value, addrLastTxnTimestamp)); - // Find next transaction to this AT since the last one (if any) codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp)); // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. @@ -546,7 +554,7 @@ public class DogecoinACCTv2 implements ACCT { // We're finished forever (finishing auto-refunds remaining balance to AT creator) codeByteBuffer.put(OpCode.FIN_IMD.compile()); } catch (CompilationException e) { - throw new IllegalStateException("Unable to compile DOGE-QORT ACCT?", e); + throw new IllegalStateException("Unable to compile ARRR-QORT ACCT?", e); } } @@ -555,7 +563,7 @@ public class DogecoinACCTv2 implements ACCT { byte[] codeBytes = new byte[codeByteBuffer.limit()]; codeByteBuffer.get(codeBytes); - assert Arrays.equals(Crypto.digest(codeBytes), DogecoinACCTv2.CODE_BYTES_HASH) + assert Arrays.equals(Crypto.digest(codeBytes), PirateChainACCTv3.CODE_BYTES_HASH) : String.format("BTCACCT.CODE_BYTES_HASH mismatch: expected %s, actual %s", HashCode.fromBytes(CODE_BYTES_HASH), HashCode.fromBytes(Crypto.digest(codeBytes))); final short ciyamAtVersion = 2; @@ -593,7 +601,7 @@ public class DogecoinACCTv2 implements ACCT { CrossChainTradeData tradeData = new CrossChainTradeData(); - tradeData.foreignBlockchain = SupportedBlockchain.DOGECOIN.name(); + tradeData.foreignBlockchain = SupportedBlockchain.PIRATECHAIN.name(); tradeData.acctName = NAME; tradeData.qortalAtAddress = atAddress; @@ -614,10 +622,10 @@ public class DogecoinACCTv2 implements ACCT { tradeData.qortalCreatorTradeAddress = Base58.encode(addressBytes); dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length); - // Creator's Dogecoin/foreign public key hash - tradeData.creatorForeignPKH = new byte[20]; + // Creator's PirateChain/foreign public key (full 33 bytes, not hashed, so ignore references to "PKH") + tradeData.creatorForeignPKH = new byte[33]; dataByteBuffer.get(tradeData.creatorForeignPKH); - dataByteBuffer.position(dataByteBuffer.position() + 32 - tradeData.creatorForeignPKH.length); // skip to 32 bytes + dataByteBuffer.position(dataByteBuffer.position() + 40 - tradeData.creatorForeignPKH.length); // skip to 40 bytes // We don't use secret-B tradeData.hashOfSecretB = null; @@ -625,7 +633,7 @@ public class DogecoinACCTv2 implements ACCT { // Redeem payout tradeData.qortAmount = dataByteBuffer.getLong(); - // Expected DOGE amount + // Expected ARRR amount tradeData.expectedForeignAmount = dataByteBuffer.getLong(); // Trade timeout @@ -649,10 +657,16 @@ public class DogecoinACCTv2 implements ACCT { // Skip pointer to message sender dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip 'trade' message data offset for partner's Dogecoin PKH + // Skip 'trade' message data offset for first 32 bytes of partner's Pirate Chain public key dataByteBuffer.position(dataByteBuffer.position() + 8); - // Skip pointer to partner's Dogecoin PKH + // Skip 'trade' message data offset for last 32 byte of partner's Pirate Chain public key + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Skip pointer to partner's Pirate Chain public key (first 32 bytes) + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Skip pointer to partner's Pirate Chain public key (last byte) dataByteBuffer.position(dataByteBuffer.position() + 8); // Skip 'trade' message data offset for hash-of-secret-A @@ -718,10 +732,10 @@ public class DogecoinACCTv2 implements ACCT { dataByteBuffer.get(hashOfSecretA); dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes - // Potential partner's Dogecoin PKH - byte[] partnerDogecoinPKH = new byte[20]; - dataByteBuffer.get(partnerDogecoinPKH); - dataByteBuffer.position(dataByteBuffer.position() + 32 - partnerDogecoinPKH.length); // skip to 32 bytes + // Potential partner's PirateChain public key + byte[] partnerPirateChainPublicKey = new byte[33]; + dataByteBuffer.get(partnerPirateChainPublicKey); + dataByteBuffer.position(dataByteBuffer.position() + 64 - partnerPirateChainPublicKey.length); // skip to 64 bytes // Partner's receiving address (if present) byte[] partnerReceivingAddress = new byte[25]; @@ -740,7 +754,7 @@ public class DogecoinACCTv2 implements ACCT { tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight; tradeData.qortalPartnerAddress = qortalRecipient; tradeData.hashOfSecretA = hashOfSecretA; - tradeData.partnerForeignPKH = partnerDogecoinPKH; + tradeData.partnerForeignPKH = partnerPirateChainPublicKey; // Not hashed tradeData.lockTimeA = lockTimeA; if (mode == AcctMode.REDEEMED) @@ -755,9 +769,9 @@ public class DogecoinACCTv2 implements ACCT { } /** Returns 'offer' MESSAGE payload for trade partner to send to AT creator's trade address. */ - public static byte[] buildOfferMessage(byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) { + public static byte[] buildOfferMessage(byte[] partnerBitcoinPublicKey, byte[] hashOfSecretA, int lockTimeA) { byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA); - return Bytes.concat(partnerBitcoinPKH, hashOfSecretA, lockTimeABytes); + return Bytes.concat(partnerBitcoinPublicKey, hashOfSecretA, lockTimeABytes); } /** Returns info extracted from 'offer' MESSAGE payload sent by trade partner to AT creator's trade address, or null if not valid. */ @@ -766,25 +780,25 @@ public class DogecoinACCTv2 implements ACCT { return null; OfferMessageData offerMessageData = new OfferMessageData(); - offerMessageData.partnerDogecoinPKH = Arrays.copyOfRange(messageData, 0, 20); - offerMessageData.hashOfSecretA = Arrays.copyOfRange(messageData, 20, 40); - offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 40); + offerMessageData.partnerPirateChainPublicKey = Arrays.copyOfRange(messageData, 0, 33); + offerMessageData.hashOfSecretA = Arrays.copyOfRange(messageData, 33, 53); + offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 53); return offerMessageData; } /** Returns 'trade' MESSAGE payload for AT creator to send to AT. */ - public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPublicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { byte[] data = new byte[TRADE_MESSAGE_LENGTH]; byte[] partnerQortalAddressBytes = Base58.decode(partnerQortalTradeAddress); byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA); byte[] refundTimeoutBytes = BitTwiddling.toBEByteArray((long) refundTimeout); System.arraycopy(partnerQortalAddressBytes, 0, data, 0, partnerQortalAddressBytes.length); - System.arraycopy(partnerBitcoinPKH, 0, data, 32, partnerBitcoinPKH.length); - System.arraycopy(refundTimeoutBytes, 0, data, 56, refundTimeoutBytes.length); - System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length); - System.arraycopy(lockTimeABytes, 0, data, 88, lockTimeABytes.length); + System.arraycopy(partnerBitcoinPublicKey, 0, data, 32, partnerBitcoinPublicKey.length); + System.arraycopy(refundTimeoutBytes, 0, data, 72, refundTimeoutBytes.length); + System.arraycopy(hashOfSecretA, 0, data, 80, hashOfSecretA.length); + System.arraycopy(lockTimeABytes, 0, data, 104, lockTimeABytes.length); return data; } diff --git a/src/main/java/org/qortal/crosschain/PirateChainHTLC.java b/src/main/java/org/qortal/crosschain/PirateChainHTLC.java new file mode 100644 index 00000000..17f7ad74 --- /dev/null +++ b/src/main/java/org/qortal/crosschain/PirateChainHTLC.java @@ -0,0 +1,404 @@ +package org.qortal.crosschain; + +import com.google.common.hash.HashCode; +import com.google.common.primitives.Bytes; +import org.bitcoinj.core.*; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptChunk; +import org.qortal.crypto.Crypto; +import org.qortal.utils.Base58; +import org.qortal.utils.BitTwiddling; + +import java.util.*; +import static org.qortal.crosschain.BitcoinyHTLC.Status; + +public class PirateChainHTLC { + + public static final int SECRET_LENGTH = 32; + public static final int MIN_LOCKTIME = 1500000000; + + public static final long NO_LOCKTIME_NO_RBF_SEQUENCE = 0xFFFFFFFFL; + public static final long LOCKTIME_NO_RBF_SEQUENCE = NO_LOCKTIME_NO_RBF_SEQUENCE - 1; + + // Assuming node's trade-bot has no more than 100 entries? + private static final int MAX_CACHE_ENTRIES = 100; + + // Max time-to-live for cache entries (milliseconds) + private static final long CACHE_TIMEOUT = 30_000L; + + @SuppressWarnings("serial") + private static final Map SECRET_CACHE = new LinkedHashMap<>(MAX_CACHE_ENTRIES + 1, 0.75F, true) { + // This method is called just after a new entry has been added + @Override + public boolean removeEldestEntry(Map.Entry eldest) { + return size() > MAX_CACHE_ENTRIES; + } + }; + private static final byte[] NO_SECRET_CACHE_ENTRY = new byte[0]; + + @SuppressWarnings("serial") + private static final Map STATUS_CACHE = new LinkedHashMap<>(MAX_CACHE_ENTRIES + 1, 0.75F, true) { + // This method is called just after a new entry has been added + @Override + public boolean removeEldestEntry(Map.Entry eldest) { + return size() > MAX_CACHE_ENTRIES; + } + }; + + /* + * OP_RETURN + OP_PUSHDATA1 + bytes (not part of actual redeem script - used for "push only" secondary output when funding P2SH) + * + * OP_IF (if top stack value isn't false) (true=refund; false=redeem) (boolean is then removed from stack) + * + * OP_CHECKLOCKTIMEVERIFY (if stack locktime greater than transaction's lock time - i.e. refunding but too soon - then fail validation) + * OP_DROP (remove locktime from top of stack) + * + * OP_CHECKSIG (check signature and public key are correct; returns 1 or 0) + * OP_ELSE (if top stack value was false, i.e. attempting to redeem) + * OP_SIZE (push length of top item - the secret - to the top of the stack) + * 32 + * OP_EQUALVERIFY (unhashed secret must be 32 bytes in length) + * OP_HASH160 (hash the secret) + * + * OP_EQUALVERIFY (ensure hash of supplied secret matches intended secret hash; transaction invalid if no match) + * + * OP_CHECKSIG (check signature and public key are correct; returns 1 or 0) + * OP_ENDIF + */ + + private static final byte[] pushOnlyPrefix = HashCode.fromString("6a4c").asBytes(); // OP_RETURN + push(redeem script) + private static final byte[] redeemScript1 = HashCode.fromString("6304").asBytes(); // OP_IF push(4 bytes locktime) + private static final byte[] redeemScript2 = HashCode.fromString("b17521").asBytes(); // OP_CHECKLOCKTIMEVERIFY OP_DROP push(33 bytes refund pubkey) + private static final byte[] redeemScript3 = HashCode.fromString("ac6782012088a914").asBytes(); // OP_CHECKSIG OP_ELSE OP_SIZE push(0x20) OP_EQUALVERIFY OP_HASH160 push(20 bytes hash of secret) + private static final byte[] redeemScript4 = HashCode.fromString("8821").asBytes(); // OP_EQUALVERIFY push(33 bytes redeem pubkey) + private static final byte[] redeemScript5 = HashCode.fromString("ac68").asBytes(); // OP_CHECKSIG OP_ENDIF + + /** + * Returns redeemScript used for cross-chain trading. + *

        + * See comments in {@link PirateChainHTLC} for more details. + * + * @param refunderPubKey 33-byte P2SH funder's public key, for refunding purposes + * @param lockTime seconds-since-epoch threshold, after which P2SH funder can claim refund + * @param redeemerPubKey 33-byte P2SH redeemer's public key + * @param hashOfSecret 20-byte HASH160 of secret, used by P2SH redeemer to claim funds + */ + public static byte[] buildScript(byte[] refunderPubKey, int lockTime, byte[] redeemerPubKey, byte[] hashOfSecret) { + return Bytes.concat(redeemScript1, BitTwiddling.toLEByteArray((int) (lockTime & 0xffffffffL)), redeemScript2, + refunderPubKey, redeemScript3, hashOfSecret, redeemScript4, redeemerPubKey, redeemScript5); + } + + /** + * Alternative to buildScript() above, this time with a prefix suitable for adding the redeem script + * to a "push only" output (via OP_RETURN followed by OP_PUSHDATA1) + * + * @param refunderPubKey 33-byte P2SH funder's public key, for refunding purposes + * @param lockTime seconds-since-epoch threshold, after which P2SH funder can claim refund + * @param redeemerPubKey 33-byte P2SH redeemer's public key + * @param hashOfSecret 20-byte HASH160 of secret, used by P2SH redeemer to claim funds + * @return + */ + public static byte[] buildScriptWithPrefix(byte[] refunderPubKey, int lockTime, byte[] redeemerPubKey, byte[] hashOfSecret) { + byte[] redeemScript = buildScript(refunderPubKey, lockTime, redeemerPubKey, hashOfSecret); + int size = redeemScript.length; + String sizeHex = Integer.toHexString(size & 0xFF); + return Bytes.concat(pushOnlyPrefix, HashCode.fromString(sizeHex).asBytes(), redeemScript); + } + + /** + * Returns 'secret', if any, given HTLC's P2SH address. + *

        + * @throws ForeignBlockchainException + */ + public static byte[] findHtlcSecret(Bitcoiny bitcoiny, String p2shAddress) throws ForeignBlockchainException { + NetworkParameters params = bitcoiny.getNetworkParameters(); + String compoundKey = String.format("%s-%s-%d", params.getId(), p2shAddress, System.currentTimeMillis() / CACHE_TIMEOUT); + + byte[] secret = SECRET_CACHE.getOrDefault(compoundKey, NO_SECRET_CACHE_ENTRY); + if (secret != NO_SECRET_CACHE_ENTRY) + return secret; + + List rawTransactions = bitcoiny.getAddressTransactions(p2shAddress); + + for (byte[] rawTransaction : rawTransactions) { + Transaction transaction = new Transaction(params, rawTransaction); + + // Cycle through inputs, looking for one that spends our HTLC + for (TransactionInput input : transaction.getInputs()) { + Script scriptSig = input.getScriptSig(); + List scriptChunks = scriptSig.getChunks(); + + // Expected number of script chunks for redeem. Refund might not have the same number. + int expectedChunkCount = 1 /*secret*/ + 1 /*sig*/ + 1 /*pubkey*/ + 1 /*redeemScript*/; + if (scriptChunks.size() != expectedChunkCount) + continue; + + // We're expecting last chunk to contain the actual redeemScript + ScriptChunk lastChunk = scriptChunks.get(scriptChunks.size() - 1); + byte[] redeemScriptBytes = lastChunk.data; + + // If non-push scripts, redeemScript will be null + if (redeemScriptBytes == null) + continue; + + byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes); + Address inputAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + + if (!inputAddress.toString().equals(p2shAddress)) + // Input isn't spending our HTLC + continue; + + secret = scriptChunks.get(0).data; + if (secret.length != PirateChainHTLC.SECRET_LENGTH) + continue; + + // Cache secret for a while + SECRET_CACHE.put(compoundKey, secret); + + return secret; + } + } + + // Cache negative result + SECRET_CACHE.put(compoundKey, null); + + return null; + } + + /** + * Returns a string containing the txid of the transaction that funded supplied p2shAddress + * We have to do this in a bit of a roundabout way due to the Pirate Light Client server omitting + * transaction hashes from the raw transaction data. + *

        + * @throws ForeignBlockchainException if error occurs + */ + public static String getFundingTxid(BitcoinyBlockchainProvider blockchain, String p2shAddress) throws ForeignBlockchainException { + byte[] ourScriptPubKey = addressToScriptPubKey(p2shAddress); + // HASH160(redeem script) for this p2shAddress + byte[] ourRedeemScriptHash = addressToRedeemScriptHash(p2shAddress); + + + // Firstly look for an unspent output + + // Note: we can't include unconfirmed transactions here because the Pirate light wallet server requires a block range + List unspentOutputs = blockchain.getUnspentOutputs(p2shAddress, false); + for (UnspentOutput unspentOutput : unspentOutputs) { + + if (!Arrays.equals(ourScriptPubKey, unspentOutput.script)) { + continue; + } + + return HashCode.fromBytes(unspentOutput.hash).toString(); + } + + + // No valid unspent outputs, so must be already spent... + + // Note: we can't include unconfirmed transactions here because the Pirate light wallet server requires a block range + List transactions = blockchain.getAddressBitcoinyTransactions(p2shAddress, BitcoinyBlockchainProvider.EXCLUDE_UNCONFIRMED); + + // Sort by confirmed first, followed by ascending height + transactions.sort(BitcoinyTransaction.CONFIRMED_FIRST.thenComparing(BitcoinyTransaction::getHeight)); + + for (BitcoinyTransaction bitcoinyTransaction : transactions) { + + // Acceptable funding is one transaction output, so we're expecting only one input + if (bitcoinyTransaction.inputs.size() != 1) + // Wrong number of inputs + continue; + + String scriptSig = bitcoinyTransaction.inputs.get(0).scriptSig; + + List scriptSigChunks = extractScriptSigChunks(HashCode.fromString(scriptSig).asBytes()); + if (scriptSigChunks.size() < 3 || scriptSigChunks.size() > 4) + // Not valid chunks for our form of HTLC + continue; + + // Last chunk is redeem script + byte[] redeemScriptBytes = scriptSigChunks.get(scriptSigChunks.size() - 1); + byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes); + if (!Arrays.equals(redeemScriptHash, ourRedeemScriptHash)) + // Not spending our specific HTLC redeem script + continue; + + return bitcoinyTransaction.inputs.get(0).outputTxHash; + + } + + return null; + } + + /** + * Returns a string containing the unspent txid of the transaction that funded supplied p2shAddress + * and is at least the value specified in minimumAmount + *

        + * @throws ForeignBlockchainException if error occurs + */ + public static String getUnspentFundingTxid(BitcoinyBlockchainProvider blockchain, String p2shAddress, long minimumAmount) throws ForeignBlockchainException { + byte[] ourScriptPubKey = addressToScriptPubKey(p2shAddress); + + // Note: we can't include unconfirmed transactions here because the Pirate light wallet server requires a block range + List unspentOutputs = blockchain.getUnspentOutputs(p2shAddress, false); + for (UnspentOutput unspentOutput : unspentOutputs) { + + if (!Arrays.equals(ourScriptPubKey, unspentOutput.script)) { + // Not funding our specific HTLC script hash + continue; + } + + if (unspentOutput.value < minimumAmount) { + // Not funding the required amount + continue; + } + + return HashCode.fromBytes(unspentOutput.hash).toString(); + } + + + // No valid unspent outputs, so must be already spent + return null; + } + + /** + * Returns HTLC status, given P2SH address and expected redeem/refund amount + *

        + * @throws ForeignBlockchainException if error occurs + */ + public static Status determineHtlcStatus(BitcoinyBlockchainProvider blockchain, String p2shAddress, long minimumAmount) throws ForeignBlockchainException { + String compoundKey = String.format("%s-%s-%d", blockchain.getNetId(), p2shAddress, System.currentTimeMillis() / CACHE_TIMEOUT); + + Status cachedStatus = STATUS_CACHE.getOrDefault(compoundKey, null); + if (cachedStatus != null) + return cachedStatus; + + byte[] ourScriptPubKey = addressToScriptPubKey(p2shAddress); + + // Note: we can't include unconfirmed transactions here because the Pirate light wallet server requires a block range + List transactions = blockchain.getAddressBitcoinyTransactions(p2shAddress, BitcoinyBlockchainProvider.EXCLUDE_UNCONFIRMED); + + // Sort by confirmed first, followed by ascending height + transactions.sort(BitcoinyTransaction.CONFIRMED_FIRST.thenComparing(BitcoinyTransaction::getHeight)); + + // Transaction cache + //Map transactionsByHash = new HashMap<>(); + // HASH160(redeem script) for this p2shAddress + byte[] ourRedeemScriptHash = addressToRedeemScriptHash(p2shAddress); + + // Check for spends first, caching full transaction info as we progress just in case we don't return in this loop + for (BitcoinyTransaction bitcoinyTransaction : transactions) { + + // Cache for possible later reuse + // transactionsByHash.put(transactionInfo.txHash, bitcoinyTransaction); + + // Acceptable funding is one transaction output, so we're expecting only one input + if (bitcoinyTransaction.inputs.size() != 1) + // Wrong number of inputs + continue; + + String scriptSig = bitcoinyTransaction.inputs.get(0).scriptSig; + + List scriptSigChunks = extractScriptSigChunks(HashCode.fromString(scriptSig).asBytes()); + if (scriptSigChunks.size() < 3 || scriptSigChunks.size() > 4) + // Not valid chunks for our form of HTLC + continue; + + // Last chunk is redeem script + byte[] redeemScriptBytes = scriptSigChunks.get(scriptSigChunks.size() - 1); + byte[] redeemScriptHash = Crypto.hash160(redeemScriptBytes); + if (!Arrays.equals(redeemScriptHash, ourRedeemScriptHash)) + // Not spending our specific HTLC redeem script + continue; + + if (scriptSigChunks.size() == 4) + // If we have 4 chunks, then secret is present, hence redeem + cachedStatus = bitcoinyTransaction.height == 0 ? Status.REDEEM_IN_PROGRESS : Status.REDEEMED; + else + cachedStatus = bitcoinyTransaction.height == 0 ? Status.REFUND_IN_PROGRESS : Status.REFUNDED; + + STATUS_CACHE.put(compoundKey, cachedStatus); + return cachedStatus; + } + + String ourScriptPubKeyHex = HashCode.fromBytes(ourScriptPubKey).toString(); + + // Check for funding + for (BitcoinyTransaction bitcoinyTransaction : transactions) { + if (bitcoinyTransaction == null) + // Should be present in map! + throw new ForeignBlockchainException("Cached Bitcoin transaction now missing?"); + + // Check outputs for our specific P2SH + for (BitcoinyTransaction.Output output : bitcoinyTransaction.outputs) { + // Check amount + if (output.value < minimumAmount) + // Output amount too small (not taking fees into account) + continue; + + String scriptPubKeyHex = output.scriptPubKey; + if (!scriptPubKeyHex.equals(ourScriptPubKeyHex)) + // Not funding our specific P2SH + continue; + + cachedStatus = bitcoinyTransaction.height == 0 ? Status.FUNDING_IN_PROGRESS : Status.FUNDED; + STATUS_CACHE.put(compoundKey, cachedStatus); + return cachedStatus; + } + } + + cachedStatus = Status.UNFUNDED; + STATUS_CACHE.put(compoundKey, cachedStatus); + return cachedStatus; + } + + private static List extractScriptSigChunks(byte[] scriptSigBytes) { + List chunks = new ArrayList<>(); + + int offset = 0; + int previousOffset = 0; + while (offset < scriptSigBytes.length) { + byte pushOp = scriptSigBytes[offset++]; + + if (pushOp < 0 || pushOp > 0x4c) + // Unacceptable OP + return Collections.emptyList(); + + // Special treatment for OP_PUSHDATA1 + if (pushOp == 0x4c) { + if (offset >= scriptSigBytes.length) + // Run out of scriptSig bytes? + return Collections.emptyList(); + + pushOp = scriptSigBytes[offset++]; + } + + previousOffset = offset; + offset += Byte.toUnsignedInt(pushOp); + + byte[] chunk = Arrays.copyOfRange(scriptSigBytes, previousOffset, offset); + chunks.add(chunk); + } + + return chunks; + } + + private static byte[] addressToScriptPubKey(String p2shAddress) { + // We want the HASH160 part of the P2SH address + byte[] p2shAddressBytes = Base58.decode(p2shAddress); + + byte[] scriptPubKey = new byte[1 + 1 + 20 + 1]; + scriptPubKey[0x00] = (byte) 0xa9; /* OP_HASH160 */ + scriptPubKey[0x01] = (byte) 0x14; /* PUSH 0x14 bytes */ + System.arraycopy(p2shAddressBytes, 1, scriptPubKey, 2, 0x14); + scriptPubKey[0x16] = (byte) 0x87; /* OP_EQUAL */ + + return scriptPubKey; + } + + private static byte[] addressToRedeemScriptHash(String p2shAddress) { + // We want the HASH160 part of the P2SH address + byte[] p2shAddressBytes = Base58.decode(p2shAddress); + + return Arrays.copyOfRange(p2shAddressBytes, 1, 1 + 20); + } + +} diff --git a/src/main/java/org/qortal/crosschain/PirateLightClient.java b/src/main/java/org/qortal/crosschain/PirateLightClient.java new file mode 100644 index 00000000..63bcd5fa --- /dev/null +++ b/src/main/java/org/qortal/crosschain/PirateLightClient.java @@ -0,0 +1,649 @@ +package org.qortal.crosschain; + +import cash.z.wallet.sdk.rpc.CompactFormats.*; +import cash.z.wallet.sdk.rpc.CompactTxStreamerGrpc; +import cash.z.wallet.sdk.rpc.Service; +import cash.z.wallet.sdk.rpc.Service.*; +import com.google.common.hash.HashCode; +import com.google.protobuf.ByteString; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.qortal.settings.Settings; +import org.qortal.transform.TransformationException; + +import java.math.BigDecimal; +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** Pirate Chain network support for querying Bitcoiny-related info like block headers, transaction outputs, etc. */ +public class PirateLightClient extends BitcoinyBlockchainProvider { + + private static final Logger LOGGER = LogManager.getLogger(PirateLightClient.class); + private static final Random RANDOM = new Random(); + + private static final int RESPONSE_TIME_READINGS = 5; + private static final long MAX_AVG_RESPONSE_TIME = 500L; // ms + + public static class Server { + String hostname; + + public enum ConnectionType { TCP, SSL } + ConnectionType connectionType; + + int port; + private List responseTimes = new ArrayList<>(); + + public Server(String hostname, ConnectionType connectionType, int port) { + this.hostname = hostname; + this.connectionType = connectionType; + this.port = port; + } + + public void addResponseTime(long responseTime) { + while (this.responseTimes.size() > RESPONSE_TIME_READINGS) { + this.responseTimes.remove(0); + } + this.responseTimes.add(responseTime); + } + + public long averageResponseTime() { + if (this.responseTimes.size() < RESPONSE_TIME_READINGS) { + // Not enough readings yet + return 0L; + } + OptionalDouble average = this.responseTimes.stream().mapToDouble(a -> a).average(); + if (average.isPresent()) { + return Double.valueOf(average.getAsDouble()).longValue(); + } + return 0L; + } + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof Server)) + return false; + + Server otherServer = (Server) other; + + return this.connectionType == otherServer.connectionType + && this.port == otherServer.port + && this.hostname.equals(otherServer.hostname); + } + + @Override + public int hashCode() { + return this.hostname.hashCode() ^ this.port; + } + + @Override + public String toString() { + return String.format("%s:%s:%d", this.connectionType.name(), this.hostname, this.port); + } + } + private Set servers = new HashSet<>(); + private List remainingServers = new ArrayList<>(); + private Set uselessServers = Collections.synchronizedSet(new HashSet<>()); + + private final String netId; + private final String expectedGenesisHash; + private final Map defaultPorts = new EnumMap<>(Server.ConnectionType.class); + private Bitcoiny blockchain; + + private final Object serverLock = new Object(); + private Server currentServer; + private ManagedChannel channel; + private int nextId = 1; + + private static final int TX_CACHE_SIZE = 1000; + @SuppressWarnings("serial") + private final Map transactionCache = Collections.synchronizedMap(new LinkedHashMap<>(TX_CACHE_SIZE + 1, 0.75F, true) { + // This method is called just after a new entry has been added + @Override + public boolean removeEldestEntry(Map.Entry eldest) { + return size() > TX_CACHE_SIZE; + } + }); + + // Constructors + + public PirateLightClient(String netId, String genesisHash, Collection initialServerList, Map defaultPorts) { + this.netId = netId; + this.expectedGenesisHash = genesisHash; + this.servers.addAll(initialServerList); + this.defaultPorts.putAll(defaultPorts); + } + + // Methods for use by other classes + + @Override + public void setBlockchain(Bitcoiny blockchain) { + this.blockchain = blockchain; + } + + @Override + public String getNetId() { + return this.netId; + } + + /** + * Returns current blockchain height. + *

        + * @throws ForeignBlockchainException if error occurs + */ + @Override + public int getCurrentHeight() throws ForeignBlockchainException { + BlockID latestBlock = this.getCompactTxStreamerStub().getLatestBlock(null); + + if (!(latestBlock instanceof BlockID)) + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain getLatestBlock gRPC"); + + return (int)latestBlock.getHeight(); + } + + /** + * Returns list of compact blocks, starting from startHeight inclusive. + *

        + * @throws ForeignBlockchainException if error occurs + * @return + */ + @Override + public List getCompactBlocks(int startHeight, int count) throws ForeignBlockchainException { + BlockID startBlock = BlockID.newBuilder().setHeight(startHeight).build(); + BlockID endBlock = BlockID.newBuilder().setHeight(startHeight + count - 1).build(); + BlockRange range = BlockRange.newBuilder().setStart(startBlock).setEnd(endBlock).build(); + + Iterator blocksIterator = this.getCompactTxStreamerStub().getBlockRange(range); + + // Map from Iterator to List + List blocks = new ArrayList<>(); + blocksIterator.forEachRemaining(blocks::add); + + return blocks; + } + + /** + * Returns list of raw block headers, starting from startHeight inclusive. + *

        + * @throws ForeignBlockchainException if error occurs + */ + @Override + public List getRawBlockHeaders(int startHeight, int count) throws ForeignBlockchainException { + BlockID startBlock = BlockID.newBuilder().setHeight(startHeight).build(); + BlockID endBlock = BlockID.newBuilder().setHeight(startHeight + count - 1).build(); + BlockRange range = BlockRange.newBuilder().setStart(startBlock).setEnd(endBlock).build(); + + Iterator blocks = this.getCompactTxStreamerStub().getBlockRange(range); + + List rawBlockHeaders = new ArrayList<>(); + + while (blocks.hasNext()) { + CompactBlock block = blocks.next(); + + if (block.getHeader() == null) { + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain getBlockRange gRPC"); + } + + rawBlockHeaders.add(block.getHeader().toByteArray()); + } + + return rawBlockHeaders; + } + + /** + * Returns list of raw block timestamps, starting from startHeight inclusive. + *

        + * @throws ForeignBlockchainException if error occurs + */ + @Override + public List getBlockTimestamps(int startHeight, int count) throws ForeignBlockchainException { + BlockID startBlock = BlockID.newBuilder().setHeight(startHeight).build(); + BlockID endBlock = BlockID.newBuilder().setHeight(startHeight + count - 1).build(); + BlockRange range = BlockRange.newBuilder().setStart(startBlock).setEnd(endBlock).build(); + + Iterator blocks = this.getCompactTxStreamerStub().getBlockRange(range); + + List rawBlockTimestamps = new ArrayList<>(); + + while (blocks.hasNext()) { + CompactBlock block = blocks.next(); + + if (block.getTime() <= 0) { + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain getBlockRange gRPC"); + } + + rawBlockTimestamps.add(Long.valueOf(block.getTime())); + } + + return rawBlockTimestamps; + } + + /** + * Returns confirmed balance, based on passed payment script. + *

        + * @return confirmed balance, or zero if script unknown + * @throws ForeignBlockchainException if there was an error + */ + @Override + public long getConfirmedBalance(byte[] script) throws ForeignBlockchainException { + throw new ForeignBlockchainException("getConfirmedBalance not yet implemented for Pirate Chain"); + } + + /** + * Returns confirmed balance, based on passed base58 encoded address. + *

        + * @return confirmed balance, or zero if address unknown + * @throws ForeignBlockchainException if there was an error + */ + @Override + public long getConfirmedAddressBalance(String base58Address) throws ForeignBlockchainException { + AddressList addressList = AddressList.newBuilder().addAddresses(base58Address).build(); + Balance balance = this.getCompactTxStreamerStub().getTaddressBalance(addressList); + + if (!(balance instanceof Balance)) + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain getConfirmedAddressBalance gRPC"); + + return balance.getValueZat(); + } + + /** + * Returns list of unspent outputs pertaining to passed address. + *

        + * @return list of unspent outputs, or empty list if address unknown + * @throws ForeignBlockchainException if there was an error. + */ + @Override + public List getUnspentOutputs(String address, boolean includeUnconfirmed) throws ForeignBlockchainException { + GetAddressUtxosArg getAddressUtxosArg = GetAddressUtxosArg.newBuilder().addAddresses(address).build(); + GetAddressUtxosReplyList replyList = this.getCompactTxStreamerStub().getAddressUtxos(getAddressUtxosArg); + + if (!(replyList instanceof GetAddressUtxosReplyList)) + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain getUnspentOutputs gRPC"); + + List unspentList = replyList.getAddressUtxosList(); + if (unspentList == null) + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain getUnspentOutputs gRPC"); + + List unspentOutputs = new ArrayList<>(); + for (GetAddressUtxosReply unspent : unspentList) { + + int height = (int)unspent.getHeight(); + // We only want unspent outputs from confirmed transactions (and definitely not mempool duplicates with height 0) + if (!includeUnconfirmed && height <= 0) + continue; + + byte[] txHash = unspent.getTxid().toByteArray(); + int outputIndex = unspent.getIndex(); + long value = unspent.getValueZat(); + byte[] script = unspent.getScript().toByteArray(); + String addressRes = unspent.getAddress(); + + unspentOutputs.add(new UnspentOutput(txHash, outputIndex, height, value, script, addressRes)); + } + + return unspentOutputs; + } + + /** + * Returns list of unspent outputs pertaining to passed payment script. + *

        + * @return list of unspent outputs, or empty list if script unknown + * @throws ForeignBlockchainException if there was an error. + */ + @Override + public List getUnspentOutputs(byte[] script, boolean includeUnconfirmed) throws ForeignBlockchainException { + String address = this.blockchain.deriveP2shAddress(script); + return this.getUnspentOutputs(address, includeUnconfirmed); + } + + /** + * Returns raw transaction for passed transaction hash. + *

        + * NOTE: Do not mutate returned byte[]! + * + * @throws ForeignBlockchainException.NotFoundException if transaction not found + * @throws ForeignBlockchainException if error occurs + */ + @Override + public byte[] getRawTransaction(String txHash) throws ForeignBlockchainException { + return getRawTransaction(HashCode.fromString(txHash).asBytes()); + } + + /** + * Returns raw transaction for passed transaction hash. + *

        + * NOTE: Do not mutate returned byte[]! + * + * @throws ForeignBlockchainException.NotFoundException if transaction not found + * @throws ForeignBlockchainException if error occurs + */ + @Override + public byte[] getRawTransaction(byte[] txHash) throws ForeignBlockchainException { + ByteString byteString = ByteString.copyFrom(txHash); + TxFilter txFilter = TxFilter.newBuilder().setHash(byteString).build(); + RawTransaction rawTransaction = this.getCompactTxStreamerStub().getTransaction(txFilter); + + if (!(rawTransaction instanceof RawTransaction)) + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain getTransaction gRPC"); + + return rawTransaction.getData().toByteArray(); + } + + /** + * Returns transaction info for passed transaction hash. + *

        + * @throws ForeignBlockchainException.NotFoundException if transaction not found + * @throws ForeignBlockchainException if error occurs + */ + @Override + public BitcoinyTransaction getTransaction(String txHash) throws ForeignBlockchainException { + // Check cache first + BitcoinyTransaction transaction = transactionCache.get(txHash); + if (transaction != null) + return transaction; + + ByteString byteString = ByteString.copyFrom(HashCode.fromString(txHash).asBytes()); + TxFilter txFilter = TxFilter.newBuilder().setHash(byteString).build(); + RawTransaction rawTransaction = this.getCompactTxStreamerStub().getTransaction(txFilter); + + if (!(rawTransaction instanceof RawTransaction)) + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain getTransaction gRPC"); + + byte[] transactionData = rawTransaction.getData().toByteArray(); + String transactionDataString = HashCode.fromBytes(transactionData).toString(); + + JSONParser parser = new JSONParser(); + JSONObject transactionJson; + try { + transactionJson = (JSONObject) parser.parse(transactionDataString); + } catch (ParseException e) { + throw new ForeignBlockchainException.NetworkException("Expected JSON string from Pirate Chain getTransaction gRPC"); + } + + Object inputsObj = transactionJson.get("vin"); + if (!(inputsObj instanceof JSONArray)) + throw new ForeignBlockchainException.NetworkException("Expected JSONArray for 'vin' from Pirate Chain getTransaction gRPC"); + + Object outputsObj = transactionJson.get("vout"); + if (!(outputsObj instanceof JSONArray)) + throw new ForeignBlockchainException.NetworkException("Expected JSONArray for 'vout' from Pirate Chain getTransaction gRPC"); + + try { + int size = ((Long) transactionJson.get("size")).intValue(); + int locktime = ((Long) transactionJson.get("locktime")).intValue(); + + // Timestamp might not be present, e.g. for unconfirmed transaction + Object timeObj = transactionJson.get("time"); + Integer timestamp = timeObj != null + ? ((Long) timeObj).intValue() + : null; + + List inputs = new ArrayList<>(); + for (Object inputObj : (JSONArray) inputsObj) { + JSONObject inputJson = (JSONObject) inputObj; + + String scriptSig = (String) ((JSONObject) inputJson.get("scriptSig")).get("hex"); + int sequence = ((Long) inputJson.get("sequence")).intValue(); + String outputTxHash = (String) inputJson.get("txid"); + int outputVout = ((Long) inputJson.get("vout")).intValue(); + + inputs.add(new BitcoinyTransaction.Input(scriptSig, sequence, outputTxHash, outputVout)); + } + + List outputs = new ArrayList<>(); + for (Object outputObj : (JSONArray) outputsObj) { + JSONObject outputJson = (JSONObject) outputObj; + + String scriptPubKey = (String) ((JSONObject) outputJson.get("scriptPubKey")).get("hex"); + long value = BigDecimal.valueOf((Double) outputJson.get("value")).setScale(8).unscaledValue().longValue(); + + // address too, if present in the "addresses" array + List addresses = null; + Object addressesObj = ((JSONObject) outputJson.get("scriptPubKey")).get("addresses"); + if (addressesObj instanceof JSONArray) { + addresses = new ArrayList<>(); + for (Object addressObj : (JSONArray) addressesObj) { + addresses.add((String) addressObj); + } + } + + // some peers return a single "address" string + Object addressObj = ((JSONObject) outputJson.get("scriptPubKey")).get("address"); + if (addressObj instanceof String) { + if (addresses == null) { + addresses = new ArrayList<>(); + } + addresses.add((String) addressObj); + } + + // For the purposes of Qortal we require all outputs to contain addresses + // Some servers omit this info, causing problems down the line with balance calculations + // Update: it turns out that they were just using a different key - "address" instead of "addresses" + // The code below can remain in place, just in case a peer returns a missing address in the future + if (addresses == null || addresses.isEmpty()) { + if (this.currentServer != null) { + this.uselessServers.add(this.currentServer); + this.closeServer(this.currentServer); + } + LOGGER.info("No output addresses returned for transaction {}", txHash); + throw new ForeignBlockchainException(String.format("No output addresses returned for transaction %s", txHash)); + } + + outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses)); + } + + transaction = new BitcoinyTransaction(txHash, size, locktime, timestamp, inputs, outputs); + + // Save into cache + transactionCache.put(txHash, transaction); + + return transaction; + } catch (NullPointerException | ClassCastException e) { + // Unexpected / invalid response from ElectrumX server + } + + throw new ForeignBlockchainException.NetworkException("Unexpected JSON format from Pirate Chain getTransaction gRPC"); + } + + /** + * Returns list of transactions, relating to passed payment script. + *

        + * @return list of related transactions, or empty list if script unknown + * @throws ForeignBlockchainException if error occurs + */ + @Override + public List getAddressTransactions(byte[] script, boolean includeUnconfirmed) throws ForeignBlockchainException { + // FUTURE: implement this if needed. Probably not very useful for private blockchains. + throw new ForeignBlockchainException("getAddressTransactions not yet implemented for Pirate Chain"); + } + + @Override + public List getAddressBitcoinyTransactions(String address, boolean includeUnconfirmed) throws ForeignBlockchainException { + try { + // Firstly we need to get the latest block + int defaultBirthday = Settings.getInstance().getArrrDefaultBirthday(); + BlockID endBlock = this.getCompactTxStreamerStub().getLatestBlock(null); + BlockID startBlock = BlockID.newBuilder().setHeight(defaultBirthday).build(); + BlockRange blockRange = BlockRange.newBuilder().setStart(startBlock).setEnd(endBlock).build(); + + TransparentAddressBlockFilter blockFilter = TransparentAddressBlockFilter.newBuilder() + .setAddress(address) + .setRange(blockRange) + .build(); + Iterator transactionIterator = this.getCompactTxStreamerStub().getTaddressTxids(blockFilter); + + // Map from Iterator to List + List rawTransactions = new ArrayList<>(); + transactionIterator.forEachRemaining(rawTransactions::add); + + List transactions = new ArrayList<>(); + + for (RawTransaction rawTransaction : rawTransactions) { + + Long height = rawTransaction.getHeight(); + if (!includeUnconfirmed && (height == null || height == 0)) + // We only want confirmed transactions + continue; + + byte[] transactionData = rawTransaction.getData().toByteArray(); + String transactionDataHex = HashCode.fromBytes(transactionData).toString(); + BitcoinyTransaction bitcoinyTransaction = PirateChain.deserializeRawTransaction(transactionDataHex); + bitcoinyTransaction.height = height.intValue(); + transactions.add(bitcoinyTransaction); + } + + return transactions; + } + catch (RuntimeException | TransformationException e) { + throw new ForeignBlockchainException(String.format("Unable to get transactions for address %s: %s", address, e.getMessage())); + } + } + + /** + * Broadcasts raw transaction to network. + *

        + * @throws ForeignBlockchainException if error occurs + */ + @Override + public void broadcastTransaction(byte[] transactionBytes) throws ForeignBlockchainException { + ByteString byteString = ByteString.copyFrom(transactionBytes); + RawTransaction rawTransaction = RawTransaction.newBuilder().setData(byteString).build(); + SendResponse sendResponse = this.getCompactTxStreamerStub().sendTransaction(rawTransaction); + + if (!(sendResponse instanceof SendResponse)) + throw new ForeignBlockchainException.NetworkException("Unexpected output from Pirate Chain broadcastTransaction gRPC"); + + if (sendResponse.getErrorCode() != 0) + throw new ForeignBlockchainException.NetworkException(String.format("Unexpected error code from Pirate Chain broadcastTransaction gRPC: %d", sendResponse.getErrorCode())); + } + + // Class-private utility methods + + + /** + * Performs RPC call, with automatic reconnection to different server if needed. + *

        + * @return "result" object from within JSON output + * @throws ForeignBlockchainException if server returns error or something goes wrong + */ + private CompactTxStreamerGrpc.CompactTxStreamerBlockingStub getCompactTxStreamerStub() throws ForeignBlockchainException { + synchronized (this.serverLock) { + if (this.remainingServers.isEmpty()) + this.remainingServers.addAll(this.servers); + + while (haveConnection()) { + // If we have more servers and the last one replied slowly, try another + if (!this.remainingServers.isEmpty()) { + long averageResponseTime = this.currentServer.averageResponseTime(); + if (averageResponseTime > MAX_AVG_RESPONSE_TIME) { + LOGGER.info("Slow average response time {}ms from {} - trying another server...", averageResponseTime, this.currentServer.hostname); + this.closeServer(); + continue; + } + } + + return CompactTxStreamerGrpc.newBlockingStub(this.channel); + +// // Didn't work, try another server... +// this.closeServer(); + } + + // Failed to perform RPC - maybe lack of servers? + LOGGER.info("Error: No connected Pirate Light servers when trying to make RPC call"); + throw new ForeignBlockchainException.NetworkException("No connected Pirate Light servers when trying to make RPC call"); + } + } + + /** Returns true if we have, or create, a connection to an ElectrumX server. */ + private boolean haveConnection() throws ForeignBlockchainException { + if (this.currentServer != null && this.channel != null && !this.channel.isShutdown()) + return true; + + while (!this.remainingServers.isEmpty()) { + Server server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size())); + LOGGER.trace(() -> String.format("Connecting to %s", server)); + + try { + this.channel = ManagedChannelBuilder.forAddress(server.hostname, server.port).build(); + + CompactTxStreamerGrpc.CompactTxStreamerBlockingStub stub = CompactTxStreamerGrpc.newBlockingStub(this.channel); + LightdInfo lightdInfo = stub.getLightdInfo(Empty.newBuilder().build()); + + if (lightdInfo == null || lightdInfo.getBlockHeight() <= 0) + continue; + + // TODO: find a way to verify that the server is using the expected chain + +// if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION) +// continue; + +// if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash)) +// continue; + + LOGGER.debug(() -> String.format("Connected to %s", server)); + this.currentServer = server; + return true; + } catch (Exception e) { + // Didn't work, try another server... + closeServer(); + } + } + + return false; + } + + /** + * Closes connection to server if it is currently connected server. + * @param server + */ + private void closeServer(Server server) { + synchronized (this.serverLock) { + if (this.currentServer == null || !this.currentServer.equals(server) || this.channel == null) { + return; + } + + // Close the gRPC managed-channel if not shut down already. + if (!this.channel.isShutdown()) { + try { + this.channel.shutdown(); + if (!this.channel.awaitTermination(10, TimeUnit.SECONDS)) { + LOGGER.warn("Timed out gracefully shutting down connection: {}. ", this.channel); + } + } catch (Exception e) { + LOGGER.error("Unexpected exception while waiting for channel termination", e); + } + } + + // Forceful shut down if still not terminated. + if (!this.channel.isTerminated()) { + try { + this.channel.shutdownNow(); + if (!this.channel.awaitTermination(15, TimeUnit.SECONDS)) { + LOGGER.warn("Timed out forcefully shutting down connection: {}. ", this.channel); + } + } catch (Exception e) { + LOGGER.error("Unexpected exception while waiting for channel termination", e); + } + } + + this.channel = null; + this.currentServer = null; + } + } + + /** Closes connection to currently connected server (if any). */ + private void closeServer() { + synchronized (this.serverLock) { + this.closeServer(this.currentServer); + } + } + +} diff --git a/src/main/java/org/qortal/crosschain/PirateWallet.java b/src/main/java/org/qortal/crosschain/PirateWallet.java new file mode 100644 index 00000000..4b95d3cc --- /dev/null +++ b/src/main/java/org/qortal/crosschain/PirateWallet.java @@ -0,0 +1,409 @@ +package org.qortal.crosschain; + +import com.rust.litewalletjni.LiteWalletJni; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.DecoderException; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.qortal.controller.PirateChainWalletController; +import org.qortal.crypto.Crypto; +import org.qortal.settings.Settings; +import org.qortal.utils.Base58; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; +import java.util.Random; + +public class PirateWallet { + + protected static final Logger LOGGER = LogManager.getLogger(PirateWallet.class); + + private byte[] entropyBytes; + private final boolean isNullSeedWallet; + private String seedPhrase; + private boolean ready = false; + + private String params; + private String saplingOutput64; + private String saplingSpend64; + + private final static String COIN_PARAMS_FILENAME = "coinparams.json"; + private final static String SAPLING_OUTPUT_FILENAME = "saplingoutput_base64"; + private final static String SAPLING_SPEND_FILENAME = "saplingspend_base64"; + + public PirateWallet(byte[] entropyBytes, boolean isNullSeedWallet) throws IOException { + this.entropyBytes = entropyBytes; + this.isNullSeedWallet = isNullSeedWallet; + + Path libDirectory = PirateChainWalletController.getRustLibOuterDirectory(); + if (!Files.exists(Paths.get(libDirectory.toString(), COIN_PARAMS_FILENAME))) { + return; + } + + this.params = Files.readString(Paths.get(libDirectory.toString(), COIN_PARAMS_FILENAME)); + this.saplingOutput64 = Files.readString(Paths.get(libDirectory.toString(), SAPLING_OUTPUT_FILENAME)); + this.saplingSpend64 = Files.readString(Paths.get(libDirectory.toString(), SAPLING_SPEND_FILENAME)); + + this.ready = this.initialize(); + } + + private boolean initialize() { + try { + LiteWalletJni.initlogging(); + + if (this.entropyBytes == null) { + return false; + } + + // Pick a random server + PirateLightClient.Server server = this.getRandomServer(); + String serverUri = String.format("https://%s:%d/", server.hostname, server.port); + + // Pirate library uses base64 encoding + String entropy64 = Base64.toBase64String(this.entropyBytes); + + // Derive seed phrase from entropy bytes + String inputSeedResponse = LiteWalletJni.getseedphrasefromentropyb64(entropy64); + JSONObject inputSeedJson = new JSONObject(inputSeedResponse); + String inputSeedPhrase = null; + if (inputSeedJson.has("seedPhrase")) { + inputSeedPhrase = inputSeedJson.getString("seedPhrase"); + } + + String wallet = this.load(); + if (wallet == null) { + // Wallet doesn't exist, so create a new one + + int birthday = Settings.getInstance().getArrrDefaultBirthday(); + if (this.isNullSeedWallet) { + try { + // Attempt to set birthday to the current block for null seed wallets + birthday = PirateChain.getInstance().blockchainProvider.getCurrentHeight(); + } + catch (ForeignBlockchainException e) { + // Use the default height + } + } + + // Initialize new wallet + String birthdayString = String.format("%d", birthday); + String outputSeedResponse = LiteWalletJni.initfromseed(serverUri, this.params, inputSeedPhrase, birthdayString, this.saplingOutput64, this.saplingSpend64); // Thread-safe. + JSONObject outputSeedJson = new JSONObject(outputSeedResponse); + String outputSeedPhrase = null; + if (outputSeedJson.has("seed")) { + outputSeedPhrase = outputSeedJson.getString("seed"); + } + + // Ensure seed phrase in response matches supplied seed phrase + if (inputSeedPhrase == null || !Objects.equals(inputSeedPhrase, outputSeedPhrase)) { + LOGGER.info("Unable to initialize Pirate Chain wallet: seed phrases do not match, or are null"); + return false; + } + + this.seedPhrase = outputSeedPhrase; + + } else { + // Restore existing wallet + String response = LiteWalletJni.initfromb64(serverUri, params, wallet, saplingOutput64, saplingSpend64); + if (response != null && !response.contains("\"initalized\":true")) { + LOGGER.info("Unable to initialize Pirate Chain wallet at {}: {}", serverUri, response); + return false; + } + this.seedPhrase = inputSeedPhrase; + } + + // Check that we're able to communicate with the library + Integer ourHeight = this.getHeight(); + return (ourHeight != null && ourHeight > 0); + + } catch (IOException | JSONException | UnsatisfiedLinkError e) { + LOGGER.info("Unable to initialize Pirate Chain wallet: {}", e.getMessage()); + } + + return false; + } + + public boolean isReady() { + return this.ready; + } + + public void setReady(boolean ready) { + this.ready = ready; + } + + public boolean entropyBytesEqual(byte[] testEntropyBytes) { + return Arrays.equals(testEntropyBytes, this.entropyBytes); + } + + private void encrypt() { + if (this.isEncrypted()) { + // Nothing to do + return; + } + + String encryptionKey = this.getEncryptionKey(); + if (encryptionKey == null) { + // Can't encrypt without a key + return; + } + + this.doEncrypt(encryptionKey); + } + + private void decrypt() { + if (!this.isEncrypted()) { + // Nothing to do + return; + } + + String encryptionKey = this.getEncryptionKey(); + if (encryptionKey == null) { + // Can't encrypt without a key + return; + } + + this.doDecrypt(encryptionKey); + } + + public void unlock() { + if (!this.isEncrypted()) { + // Nothing to do + return; + } + + String encryptionKey = this.getEncryptionKey(); + if (encryptionKey == null) { + // Can't encrypt without a key + return; + } + + this.doUnlock(encryptionKey); + } + + public boolean save() throws IOException { + if (!isInitialized()) { + LOGGER.info("Error: can't save wallet, because no wallet it initialized"); + return false; + } + if (this.isNullSeedWallet()) { + // Don't save wallets that have a null seed + return false; + } + + // Encrypt first (will do nothing if already encrypted) + this.encrypt(); + + String wallet64 = LiteWalletJni.save(); + byte[] wallet; + try { + wallet = Base64.decode(wallet64); + } + catch (DecoderException e) { + LOGGER.info("Unable to decode wallet"); + return false; + } + if (wallet == null) { + LOGGER.info("Unable to save wallet"); + return false; + } + + Path walletPath = this.getCurrentWalletPath(); + Files.createDirectories(walletPath.getParent()); + Files.write(walletPath, wallet, StandardOpenOption.CREATE); + + LOGGER.debug("Saved Pirate Chain wallet"); + + return true; + } + + public String load() throws IOException { + if (this.isNullSeedWallet()) { + // Don't load wallets that have a null seed + return null; + } + Path walletPath = this.getCurrentWalletPath(); + if (!Files.exists(walletPath)) { + return null; + } + byte[] wallet = Files.readAllBytes(walletPath); + if (wallet == null) { + return null; + } + String wallet64 = Base64.toBase64String(wallet); + return wallet64; + } + + private String getEntropyHash58() { + if (this.entropyBytes == null) { + return null; + } + byte[] entropyHash = Crypto.digest(this.entropyBytes); + return Base58.encode(entropyHash); + } + + public String getSeedPhrase() { + return this.seedPhrase; + } + + private String getEncryptionKey() { + if (this.entropyBytes == null) { + return null; + } + + // Prefix the bytes with a (deterministic) string, to ensure that the resulting hash is different + String prefix = "ARRRWalletEncryption"; + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + outputStream.write(prefix.getBytes(StandardCharsets.UTF_8)); + outputStream.write(this.entropyBytes); + + } catch (IOException e) { + return null; + } + + byte[] encryptionKeyHash = Crypto.digest(outputStream.toByteArray()); + return Base58.encode(encryptionKeyHash); + } + + private Path getCurrentWalletPath() { + String entropyHash58 = this.getEntropyHash58(); + String filename = String.format("wallet-%s.dat", entropyHash58); + return Paths.get(Settings.getInstance().getWalletsPath(), "PirateChain", filename); + } + + public boolean isInitialized() { + return this.entropyBytes != null && this.ready; + } + + public boolean isSynchronized() { + Integer height = this.getHeight(); + Integer chainTip = this.getChainTip(); + + if (height == null || chainTip == null) { + return false; + } + + // Assume synchronized if within 2 blocks of the chain tip + return height >= (chainTip - 2); + } + + + // APIs + + public Integer getHeight() { + String response = LiteWalletJni.execute("height", ""); + JSONObject json = new JSONObject(response); + if (json.has("height")) { + return json.getInt("height"); + } + return null; + } + + public Integer getChainTip() { + String response = LiteWalletJni.execute("info", ""); + JSONObject json = new JSONObject(response); + if (json.has("latest_block_height")) { + return json.getInt("latest_block_height"); + } + return null; + } + + public boolean isNullSeedWallet() { + return this.isNullSeedWallet; + } + + public Boolean isEncrypted() { + String response = LiteWalletJni.execute("encryptionstatus", ""); + JSONObject json = new JSONObject(response); + if (json.has("encrypted")) { + return json.getBoolean("encrypted"); + } + return null; + } + + public boolean doEncrypt(String key) { + String response = LiteWalletJni.execute("encrypt", key); + JSONObject json = new JSONObject(response); + String result = json.getString("result"); + if (json.has("result")) { + return (Objects.equals(result, "success")); + } + return false; + } + + public boolean doDecrypt(String key) { + String response = LiteWalletJni.execute("decrypt", key); + JSONObject json = new JSONObject(response); + String result = json.getString("result"); + if (json.has("result")) { + return (Objects.equals(result, "success")); + } + return false; + } + + public boolean doUnlock(String key) { + String response = LiteWalletJni.execute("unlock", key); + JSONObject json = new JSONObject(response); + String result = json.getString("result"); + if (json.has("result")) { + return (Objects.equals(result, "success")); + } + return false; + } + + public String getWalletAddress() { + // Get balance, which also contains wallet addresses + String response = LiteWalletJni.execute("balance", ""); + JSONObject json = new JSONObject(response); + String address = null; + + if (json.has("z_addresses")) { + JSONArray z_addresses = json.getJSONArray("z_addresses"); + + if (z_addresses != null && !z_addresses.isEmpty()) { + JSONObject firstAddress = z_addresses.getJSONObject(0); + if (firstAddress.has("address")) { + address = firstAddress.getString("address"); + } + } + } + return address; + } + + public String getPrivateKey() { + String response = LiteWalletJni.execute("export", ""); + JSONArray addressesJson = new JSONArray(response); + if (!addressesJson.isEmpty()) { + JSONObject addressJson = addressesJson.getJSONObject(0); + if (addressJson.has("private_key")) { + //String address = addressJson.getString("address"); + String privateKey = addressJson.getString("private_key"); + //String viewingKey = addressJson.getString("viewing_key"); + + return privateKey; + } + } + return null; + } + + public PirateLightClient.Server getRandomServer() { + PirateChain.PirateChainNet pirateChainNet = Settings.getInstance().getPirateChainNet(); + Collection servers = pirateChainNet.getServers(); + Random random = new Random(); + int index = random.nextInt(servers.size()); + return (PirateLightClient.Server) servers.toArray()[index]; + } + +} diff --git a/src/main/java/org/qortal/crosschain/Ravencoin.java b/src/main/java/org/qortal/crosschain/Ravencoin.java index d65c0a13..7bf5b20f 100644 --- a/src/main/java/org/qortal/crosschain/Ravencoin.java +++ b/src/main/java/org/qortal/crosschain/Ravencoin.java @@ -138,6 +138,8 @@ public class Ravencoin extends Bitcoiny { Context bitcoinjContext = new Context(ravencoinNet.getParams()); instance = new Ravencoin(ravencoinNet, electrumX, bitcoinjContext, CURRENCY_CODE); + + electrumX.setBlockchain(instance); } return instance; diff --git a/src/main/java/org/qortal/crosschain/SimpleTransaction.java b/src/main/java/org/qortal/crosschain/SimpleTransaction.java index 27c9f9e3..b61544d1 100644 --- a/src/main/java/org/qortal/crosschain/SimpleTransaction.java +++ b/src/main/java/org/qortal/crosschain/SimpleTransaction.java @@ -7,11 +7,12 @@ import java.util.List; @XmlAccessorType(XmlAccessType.FIELD) public class SimpleTransaction { private String txHash; - private Integer timestamp; + private Long timestamp; private long totalAmount; private long feeAmount; private List inputs; private List outputs; + private String memo; @XmlAccessorType(XmlAccessType.FIELD) @@ -74,20 +75,21 @@ public class SimpleTransaction { public SimpleTransaction() { } - public SimpleTransaction(String txHash, Integer timestamp, long totalAmount, long feeAmount, List inputs, List outputs) { + public SimpleTransaction(String txHash, Long timestamp, long totalAmount, long feeAmount, List inputs, List outputs, String memo) { this.txHash = txHash; this.timestamp = timestamp; this.totalAmount = totalAmount; this.feeAmount = feeAmount; this.inputs = inputs; this.outputs = outputs; + this.memo = memo; } public String getTxHash() { return txHash; } - public Integer getTimestamp() { + public Long getTimestamp() { return timestamp; } diff --git a/src/main/java/org/qortal/crosschain/SupportedBlockchain.java b/src/main/java/org/qortal/crosschain/SupportedBlockchain.java index b249293c..5ddb6aec 100644 --- a/src/main/java/org/qortal/crosschain/SupportedBlockchain.java +++ b/src/main/java/org/qortal/crosschain/SupportedBlockchain.java @@ -29,7 +29,6 @@ public enum SupportedBlockchain { LITECOIN(Arrays.asList( Triple.valueOf(LitecoinACCTv1.NAME, LitecoinACCTv1.CODE_BYTES_HASH, LitecoinACCTv1::getInstance), - Triple.valueOf(LitecoinACCTv2.NAME, LitecoinACCTv2.CODE_BYTES_HASH, LitecoinACCTv2::getInstance), Triple.valueOf(LitecoinACCTv3.NAME, LitecoinACCTv3.CODE_BYTES_HASH, LitecoinACCTv3::getInstance) )) { @Override @@ -45,7 +44,6 @@ public enum SupportedBlockchain { DOGECOIN(Arrays.asList( Triple.valueOf(DogecoinACCTv1.NAME, DogecoinACCTv1.CODE_BYTES_HASH, DogecoinACCTv1::getInstance), - Triple.valueOf(DogecoinACCTv2.NAME, DogecoinACCTv2.CODE_BYTES_HASH, DogecoinACCTv2::getInstance), Triple.valueOf(DogecoinACCTv3.NAME, DogecoinACCTv3.CODE_BYTES_HASH, DogecoinACCTv3::getInstance) )) { @Override @@ -85,6 +83,20 @@ public enum SupportedBlockchain { public ACCT getLatestAcct() { return RavencoinACCTv3.getInstance(); } + }, + + PIRATECHAIN(Arrays.asList( + Triple.valueOf(PirateChainACCTv3.NAME, PirateChainACCTv3.CODE_BYTES_HASH, PirateChainACCTv3::getInstance) + )) { + @Override + public ForeignBlockchain getInstance() { + return PirateChain.getInstance(); + } + + @Override + public ACCT getLatestAcct() { + return PirateChainACCTv3.getInstance(); + } }; private static final Map> supportedAcctsByCodeHash = Arrays.stream(SupportedBlockchain.values()) diff --git a/src/main/java/org/qortal/crosschain/UnspentOutput.java b/src/main/java/org/qortal/crosschain/UnspentOutput.java index 86aa533d..9929d3be 100644 --- a/src/main/java/org/qortal/crosschain/UnspentOutput.java +++ b/src/main/java/org/qortal/crosschain/UnspentOutput.java @@ -7,10 +7,20 @@ public class UnspentOutput { public final int height; public final long value; - public UnspentOutput(byte[] hash, int index, int height, long value) { + // Optional fields returned by Pirate Light Client server + public final byte[] script; + public final String address; + + public UnspentOutput(byte[] hash, int index, int height, long value, byte[] script, String address) { this.hash = hash; this.index = index; this.height = height; this.value = value; + this.script = script; + this.address = address; + } + + public UnspentOutput(byte[] hash, int index, int height, long value) { + this(hash, index, height, value, null, null); } } \ No newline at end of file diff --git a/src/main/java/org/qortal/crypto/BouncyCastle25519.java b/src/main/java/org/qortal/crypto/BouncyCastle25519.java deleted file mode 100644 index 1a2e0de9..00000000 --- a/src/main/java/org/qortal/crypto/BouncyCastle25519.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.qortal.crypto; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; - -import org.bouncycastle.crypto.Digest; -import org.bouncycastle.math.ec.rfc7748.X25519; -import org.bouncycastle.math.ec.rfc7748.X25519Field; -import org.bouncycastle.math.ec.rfc8032.Ed25519; - -/** Additions to BouncyCastle providing Ed25519 to X25519 key conversion. */ -public class BouncyCastle25519 { - - private static final Class pointAffineClass; - private static final Constructor pointAffineCtor; - private static final Method decodePointVarMethod; - private static final Field yField; - - static { - try { - Class ed25519Class = Ed25519.class; - pointAffineClass = Arrays.stream(ed25519Class.getDeclaredClasses()).filter(clazz -> clazz.getSimpleName().equals("PointAffine")).findFirst().get(); - if (pointAffineClass == null) - throw new ClassNotFoundException("Can't locate PointExt inner class inside Ed25519"); - - decodePointVarMethod = ed25519Class.getDeclaredMethod("decodePointVar", byte[].class, int.class, boolean.class, pointAffineClass); - decodePointVarMethod.setAccessible(true); - - pointAffineCtor = pointAffineClass.getDeclaredConstructors()[0]; - pointAffineCtor.setAccessible(true); - - yField = pointAffineClass.getDeclaredField("y"); - yField.setAccessible(true); - } catch (NoSuchMethodException | SecurityException | IllegalArgumentException | NoSuchFieldException | ClassNotFoundException e) { - throw new RuntimeException("Can't initialize BouncyCastle25519 shim", e); - } - } - - private static int[] obtainYFromPublicKey(byte[] ed25519PublicKey) { - try { - Object pA = pointAffineCtor.newInstance(); - - Boolean result = (Boolean) decodePointVarMethod.invoke(null, ed25519PublicKey, 0, true, pA); - if (result == null || !result) - return null; - - return (int[]) yField.get(pA); - } catch (SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException("Can't reflect into BouncyCastle", e); - } - } - - public static byte[] toX25519PublicKey(byte[] ed25519PublicKey) { - int[] one = new int[X25519Field.SIZE]; - X25519Field.one(one); - - int[] y = obtainYFromPublicKey(ed25519PublicKey); - - int[] oneMinusY = new int[X25519Field.SIZE]; - X25519Field.sub(one, y, oneMinusY); - - int[] onePlusY = new int[X25519Field.SIZE]; - X25519Field.add(one, y, onePlusY); - - int[] oneMinusYInverted = new int[X25519Field.SIZE]; - X25519Field.inv(oneMinusY, oneMinusYInverted); - - int[] u = new int[X25519Field.SIZE]; - X25519Field.mul(onePlusY, oneMinusYInverted, u); - - X25519Field.normalize(u); - - byte[] x25519PublicKey = new byte[X25519.SCALAR_SIZE]; - X25519Field.encode(u, x25519PublicKey, 0); - - return x25519PublicKey; - } - - public static byte[] toX25519PrivateKey(byte[] ed25519PrivateKey) { - Digest d = Ed25519.createPrehash(); - byte[] h = new byte[d.getDigestSize()]; - - d.update(ed25519PrivateKey, 0, ed25519PrivateKey.length); - d.doFinal(h, 0); - - byte[] s = new byte[X25519.SCALAR_SIZE]; - - System.arraycopy(h, 0, s, 0, X25519.SCALAR_SIZE); - s[0] &= 0xF8; - s[X25519.SCALAR_SIZE - 1] &= 0x7F; - s[X25519.SCALAR_SIZE - 1] |= 0x40; - - return s; - } - -} diff --git a/src/main/java/org/qortal/crypto/BouncyCastleEd25519.java b/src/main/java/org/qortal/crypto/BouncyCastleEd25519.java new file mode 100644 index 00000000..ebcf0f97 --- /dev/null +++ b/src/main/java/org/qortal/crypto/BouncyCastleEd25519.java @@ -0,0 +1,1427 @@ +package org.qortal.crypto; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA512Digest; +import org.bouncycastle.math.ec.rfc7748.X25519; +import org.bouncycastle.math.ec.rfc7748.X25519Field; +import org.bouncycastle.math.raw.Interleave; +import org.bouncycastle.math.raw.Nat; +import org.bouncycastle.math.raw.Nat256; +import org.bouncycastle.util.Arrays; + +/** + * Duplicate of {@link org.bouncycastle.math.ec.rfc8032.Ed25519}, + * but with {@code private} modifiers replaced with {@code protected}, + * to allow for extension by {@link org.qortal.crypto.Qortal25519Extras}. + */ +public abstract class BouncyCastleEd25519 +{ + // -x^2 + y^2 == 1 + 0x52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3 * x^2 * y^2 + + public static final class Algorithm + { + public static final int Ed25519 = 0; + public static final int Ed25519ctx = 1; + public static final int Ed25519ph = 2; + } + + protected static class F extends X25519Field {}; + + protected static final long M08L = 0x000000FFL; + protected static final long M28L = 0x0FFFFFFFL; + protected static final long M32L = 0xFFFFFFFFL; + + protected static final int POINT_BYTES = 32; + protected static final int SCALAR_INTS = 8; + protected static final int SCALAR_BYTES = SCALAR_INTS * 4; + + public static final int PREHASH_SIZE = 64; + public static final int PUBLIC_KEY_SIZE = POINT_BYTES; + public static final int SECRET_KEY_SIZE = 32; + public static final int SIGNATURE_SIZE = POINT_BYTES + SCALAR_BYTES; + + // "SigEd25519 no Ed25519 collisions" + protected static final byte[] DOM2_PREFIX = new byte[]{ 0x53, 0x69, 0x67, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, + 0x20, 0x6e, 0x6f, 0x20, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x73 }; + + protected static final int[] P = new int[]{ 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x7FFFFFFF }; + protected static final int[] L = new int[]{ 0x5CF5D3ED, 0x5812631A, 0xA2F79CD6, 0x14DEF9DE, 0x00000000, 0x00000000, + 0x00000000, 0x10000000 }; + + protected static final int L0 = 0xFCF5D3ED; // L0:26/-- + protected static final int L1 = 0x012631A6; // L1:24/22 + protected static final int L2 = 0x079CD658; // L2:27/-- + protected static final int L3 = 0xFF9DEA2F; // L3:23/-- + protected static final int L4 = 0x000014DF; // L4:12/11 + + protected static final int[] B_x = new int[]{ 0x0325D51A, 0x018B5823, 0x007B2C95, 0x0304A92D, 0x00D2598E, 0x01D6DC5C, + 0x01388C7F, 0x013FEC0A, 0x029E6B72, 0x0042D26D }; + protected static final int[] B_y = new int[]{ 0x02666658, 0x01999999, 0x00666666, 0x03333333, 0x00CCCCCC, 0x02666666, + 0x01999999, 0x00666666, 0x03333333, 0x00CCCCCC, }; + protected static final int[] C_d = new int[]{ 0x035978A3, 0x02D37284, 0x018AB75E, 0x026A0A0E, 0x0000E014, 0x0379E898, + 0x01D01E5D, 0x01E738CC, 0x03715B7F, 0x00A406D9 }; + protected static final int[] C_d2 = new int[]{ 0x02B2F159, 0x01A6E509, 0x01156EBD, 0x00D4141D, 0x0001C029, 0x02F3D130, + 0x03A03CBB, 0x01CE7198, 0x02E2B6FF, 0x00480DB3 }; + protected static final int[] C_d4 = new int[]{ 0x0165E2B2, 0x034DCA13, 0x002ADD7A, 0x01A8283B, 0x00038052, 0x01E7A260, + 0x03407977, 0x019CE331, 0x01C56DFF, 0x00901B67 }; + + protected static final int WNAF_WIDTH_BASE = 7; + + protected static final int PRECOMP_BLOCKS = 8; + protected static final int PRECOMP_TEETH = 4; + protected static final int PRECOMP_SPACING = 8; + protected static final int PRECOMP_POINTS = 1 << (PRECOMP_TEETH - 1); + protected static final int PRECOMP_MASK = PRECOMP_POINTS - 1; + + protected static final Object precompLock = new Object(); + // TODO[ed25519] Convert to PointPrecomp + protected static PointExt[] precompBaseTable = null; + protected static int[] precompBase = null; + + protected static class PointAccum + { + int[] x = F.create(); + int[] y = F.create(); + int[] z = F.create(); + int[] u = F.create(); + int[] v = F.create(); + } + + protected static class PointAffine + { + int[] x = F.create(); + int[] y = F.create(); + } + + protected static class PointExt + { + int[] x = F.create(); + int[] y = F.create(); + int[] z = F.create(); + int[] t = F.create(); + } + + protected static class PointPrecomp + { + int[] ypx_h = F.create(); + int[] ymx_h = F.create(); + int[] xyd = F.create(); + } + + protected static byte[] calculateS(byte[] r, byte[] k, byte[] s) + { + int[] t = new int[SCALAR_INTS * 2]; decodeScalar(r, 0, t); + int[] u = new int[SCALAR_INTS]; decodeScalar(k, 0, u); + int[] v = new int[SCALAR_INTS]; decodeScalar(s, 0, v); + + Nat256.mulAddTo(u, v, t); + + byte[] result = new byte[SCALAR_BYTES * 2]; + for (int i = 0; i < t.length; ++i) + { + encode32(t[i], result, i * 4); + } + return reduceScalar(result); + } + + protected static boolean checkContextVar(byte[] ctx , byte phflag) + { + return ctx == null && phflag == 0x00 + || ctx != null && ctx.length < 256; + } + + protected static int checkPoint(int[] x, int[] y) + { + int[] t = F.create(); + int[] u = F.create(); + int[] v = F.create(); + + F.sqr(x, u); + F.sqr(y, v); + F.mul(u, v, t); + F.sub(v, u, v); + F.mul(t, C_d, t); + F.addOne(t); + F.sub(t, v, t); + F.normalize(t); + + return F.isZero(t); + } + + protected static int checkPoint(int[] x, int[] y, int[] z) + { + int[] t = F.create(); + int[] u = F.create(); + int[] v = F.create(); + int[] w = F.create(); + + F.sqr(x, u); + F.sqr(y, v); + F.sqr(z, w); + F.mul(u, v, t); + F.sub(v, u, v); + F.mul(v, w, v); + F.sqr(w, w); + F.mul(t, C_d, t); + F.add(t, w, t); + F.sub(t, v, t); + F.normalize(t); + + return F.isZero(t); + } + + protected static boolean checkPointVar(byte[] p) + { + int[] t = new int[8]; + decode32(p, 0, t, 0, 8); + t[7] &= 0x7FFFFFFF; + return !Nat256.gte(t, P); + } + + protected static boolean checkScalarVar(byte[] s) + { + int[] n = new int[SCALAR_INTS]; + decodeScalar(s, 0, n); + return !Nat256.gte(n, L); + } + + protected static Digest createDigest() + { + return new SHA512Digest(); + } + + public static Digest createPrehash() + { + return createDigest(); + } + + protected static int decode24(byte[] bs, int off) + { + int n = bs[ off] & 0xFF; + n |= (bs[++off] & 0xFF) << 8; + n |= (bs[++off] & 0xFF) << 16; + return n; + } + + protected static int decode32(byte[] bs, int off) + { + int n = bs[off] & 0xFF; + n |= (bs[++off] & 0xFF) << 8; + n |= (bs[++off] & 0xFF) << 16; + n |= bs[++off] << 24; + return n; + } + + protected static void decode32(byte[] bs, int bsOff, int[] n, int nOff, int nLen) + { + for (int i = 0; i < nLen; ++i) + { + n[nOff + i] = decode32(bs, bsOff + i * 4); + } + } + + protected static boolean decodePointVar(byte[] p, int pOff, boolean negate, PointAffine r) + { + byte[] py = Arrays.copyOfRange(p, pOff, pOff + POINT_BYTES); + if (!checkPointVar(py)) + { + return false; + } + + int x_0 = (py[POINT_BYTES - 1] & 0x80) >>> 7; + py[POINT_BYTES - 1] &= 0x7F; + + F.decode(py, 0, r.y); + + int[] u = F.create(); + int[] v = F.create(); + + F.sqr(r.y, u); + F.mul(C_d, u, v); + F.subOne(u); + F.addOne(v); + + if (!F.sqrtRatioVar(u, v, r.x)) + { + return false; + } + + F.normalize(r.x); + if (x_0 == 1 && F.isZeroVar(r.x)) + { + return false; + } + + if (negate ^ (x_0 != (r.x[0] & 1))) + { + F.negate(r.x, r.x); + } + + return true; + } + + protected static void decodeScalar(byte[] k, int kOff, int[] n) + { + decode32(k, kOff, n, 0, SCALAR_INTS); + } + + protected static void dom2(Digest d, byte phflag, byte[] ctx) + { + if (ctx != null) + { + int n = DOM2_PREFIX.length; + byte[] t = new byte[n + 2 + ctx.length]; + System.arraycopy(DOM2_PREFIX, 0, t, 0, n); + t[n] = phflag; + t[n + 1] = (byte)ctx.length; + System.arraycopy(ctx, 0, t, n + 2, ctx.length); + + d.update(t, 0, t.length); + } + } + + protected static void encode24(int n, byte[] bs, int off) + { + bs[ off] = (byte)(n ); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n >>> 16); + } + + protected static void encode32(int n, byte[] bs, int off) + { + bs[ off] = (byte)(n ); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n >>> 16); + bs[++off] = (byte)(n >>> 24); + } + + protected static void encode56(long n, byte[] bs, int off) + { + encode32((int)n, bs, off); + encode24((int)(n >>> 32), bs, off + 4); + } + + protected static int encodePoint(PointAccum p, byte[] r, int rOff) + { + int[] x = F.create(); + int[] y = F.create(); + + F.inv(p.z, y); + F.mul(p.x, y, x); + F.mul(p.y, y, y); + F.normalize(x); + F.normalize(y); + + int result = checkPoint(x, y); + + F.encode(y, r, rOff); + r[rOff + POINT_BYTES - 1] |= ((x[0] & 1) << 7); + + return result; + } + + public static void generatePrivateKey(SecureRandom random, byte[] k) + { + random.nextBytes(k); + } + + public static void generatePublicKey(byte[] sk, int skOff, byte[] pk, int pkOff) + { + Digest d = createDigest(); + byte[] h = new byte[d.getDigestSize()]; + + d.update(sk, skOff, SECRET_KEY_SIZE); + d.doFinal(h, 0); + + byte[] s = new byte[SCALAR_BYTES]; + pruneScalar(h, 0, s); + + scalarMultBaseEncoded(s, pk, pkOff); + } + + protected static int getWindow4(int[] x, int n) + { + int w = n >>> 3, b = (n & 7) << 2; + return (x[w] >>> b) & 15; + } + + protected static byte[] getWnafVar(int[] n, int width) + { +// assert n[SCALAR_INTS - 1] >>> 28 == 0; + + int[] t = new int[SCALAR_INTS * 2]; + { + int tPos = t.length, c = 0; + int i = SCALAR_INTS; + while (--i >= 0) + { + int next = n[i]; + t[--tPos] = (next >>> 16) | (c << 16); + t[--tPos] = c = next; + } + } + + byte[] ws = new byte[253]; + + final int pow2 = 1 << width; + final int mask = pow2 - 1; + final int sign = pow2 >>> 1; + + int j = 0, carry = 0; + for (int i = 0; i < t.length; ++i, j -= 16) + { + int word = t[i]; + while (j < 16) + { + int word16 = word >>> j; + int bit = word16 & 1; + + if (bit == carry) + { + ++j; + continue; + } + + int digit = (word16 & mask) + carry; + carry = digit & sign; + digit -= (carry << 1); + carry >>>= (width - 1); + + ws[(i << 4) + j] = (byte)digit; + + j += width; + } + } + +// assert carry == 0; + + return ws; + } + + protected static void implSign(Digest d, byte[] h, byte[] s, byte[] pk, int pkOff, byte[] ctx, byte phflag, byte[] m, + int mOff, int mLen, byte[] sig, int sigOff) + { + dom2(d, phflag, ctx); + d.update(h, SCALAR_BYTES, SCALAR_BYTES); + d.update(m, mOff, mLen); + d.doFinal(h, 0); + + byte[] r = reduceScalar(h); + byte[] R = new byte[POINT_BYTES]; + scalarMultBaseEncoded(r, R, 0); + + dom2(d, phflag, ctx); + d.update(R, 0, POINT_BYTES); + d.update(pk, pkOff, POINT_BYTES); + d.update(m, mOff, mLen); + d.doFinal(h, 0); + + byte[] k = reduceScalar(h); + byte[] S = calculateS(r, k, s); + + System.arraycopy(R, 0, sig, sigOff, POINT_BYTES); + System.arraycopy(S, 0, sig, sigOff + POINT_BYTES, SCALAR_BYTES); + } + + protected static void implSign(byte[] sk, int skOff, byte[] ctx, byte phflag, byte[] m, int mOff, int mLen, + byte[] sig, int sigOff) + { + if (!checkContextVar(ctx, phflag)) + { + throw new IllegalArgumentException("ctx"); + } + + Digest d = createDigest(); + byte[] h = new byte[d.getDigestSize()]; + + d.update(sk, skOff, SECRET_KEY_SIZE); + d.doFinal(h, 0); + + byte[] s = new byte[SCALAR_BYTES]; + pruneScalar(h, 0, s); + + byte[] pk = new byte[POINT_BYTES]; + scalarMultBaseEncoded(s, pk, 0); + + implSign(d, h, s, pk, 0, ctx, phflag, m, mOff, mLen, sig, sigOff); + } + + protected static void implSign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte phflag, + byte[] m, int mOff, int mLen, byte[] sig, int sigOff) + { + if (!checkContextVar(ctx, phflag)) + { + throw new IllegalArgumentException("ctx"); + } + + Digest d = createDigest(); + byte[] h = new byte[d.getDigestSize()]; + + d.update(sk, skOff, SECRET_KEY_SIZE); + d.doFinal(h, 0); + + byte[] s = new byte[SCALAR_BYTES]; + pruneScalar(h, 0, s); + + implSign(d, h, s, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff); + } + + protected static boolean implVerify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte phflag, byte[] m, + int mOff, int mLen) + { + if (!checkContextVar(ctx, phflag)) + { + throw new IllegalArgumentException("ctx"); + } + + byte[] R = Arrays.copyOfRange(sig, sigOff, sigOff + POINT_BYTES); + byte[] S = Arrays.copyOfRange(sig, sigOff + POINT_BYTES, sigOff + SIGNATURE_SIZE); + + if (!checkPointVar(R)) + { + return false; + } + if (!checkScalarVar(S)) + { + return false; + } + + PointAffine pA = new PointAffine(); + if (!decodePointVar(pk, pkOff, true, pA)) + { + return false; + } + + Digest d = createDigest(); + byte[] h = new byte[d.getDigestSize()]; + + dom2(d, phflag, ctx); + d.update(R, 0, POINT_BYTES); + d.update(pk, pkOff, POINT_BYTES); + d.update(m, mOff, mLen); + d.doFinal(h, 0); + + byte[] k = reduceScalar(h); + + int[] nS = new int[SCALAR_INTS]; + decodeScalar(S, 0, nS); + + int[] nA = new int[SCALAR_INTS]; + decodeScalar(k, 0, nA); + + PointAccum pR = new PointAccum(); + scalarMultStrausVar(nS, nA, pA, pR); + + byte[] check = new byte[POINT_BYTES]; + return 0 != encodePoint(pR, check, 0) && Arrays.areEqual(check, R); + } + + protected static void pointAdd(PointExt p, PointAccum r) + { + int[] a = F.create(); + int[] b = F.create(); + int[] c = F.create(); + int[] d = F.create(); + int[] e = r.u; + int[] f = F.create(); + int[] g = F.create(); + int[] h = r.v; + + F.apm(r.y, r.x, b, a); + F.apm(p.y, p.x, d, c); + F.mul(a, c, a); + F.mul(b, d, b); + F.mul(r.u, r.v, c); + F.mul(c, p.t, c); + F.mul(c, C_d2, c); + F.mul(r.z, p.z, d); + F.add(d, d, d); + F.apm(b, a, h, e); + F.apm(d, c, g, f); + F.carry(g); + F.mul(e, f, r.x); + F.mul(g, h, r.y); + F.mul(f, g, r.z); + } + + protected static void pointAdd(PointExt p, PointExt r) + { + int[] a = F.create(); + int[] b = F.create(); + int[] c = F.create(); + int[] d = F.create(); + int[] e = F.create(); + int[] f = F.create(); + int[] g = F.create(); + int[] h = F.create(); + + F.apm(p.y, p.x, b, a); + F.apm(r.y, r.x, d, c); + F.mul(a, c, a); + F.mul(b, d, b); + F.mul(p.t, r.t, c); + F.mul(c, C_d2, c); + F.mul(p.z, r.z, d); + F.add(d, d, d); + F.apm(b, a, h, e); + F.apm(d, c, g, f); + F.carry(g); + F.mul(e, f, r.x); + F.mul(g, h, r.y); + F.mul(f, g, r.z); + F.mul(e, h, r.t); + } + + protected static void pointAddVar(boolean negate, PointExt p, PointAccum r) + { + int[] a = F.create(); + int[] b = F.create(); + int[] c = F.create(); + int[] d = F.create(); + int[] e = r.u; + int[] f = F.create(); + int[] g = F.create(); + int[] h = r.v; + + int[] nc, nd, nf, ng; + if (negate) + { + nc = d; nd = c; nf = g; ng = f; + } + else + { + nc = c; nd = d; nf = f; ng = g; + } + + F.apm(r.y, r.x, b, a); + F.apm(p.y, p.x, nd, nc); + F.mul(a, c, a); + F.mul(b, d, b); + F.mul(r.u, r.v, c); + F.mul(c, p.t, c); + F.mul(c, C_d2, c); + F.mul(r.z, p.z, d); + F.add(d, d, d); + F.apm(b, a, h, e); + F.apm(d, c, ng, nf); + F.carry(ng); + F.mul(e, f, r.x); + F.mul(g, h, r.y); + F.mul(f, g, r.z); + } + + protected static void pointAddVar(boolean negate, PointExt p, PointExt q, PointExt r) + { + int[] a = F.create(); + int[] b = F.create(); + int[] c = F.create(); + int[] d = F.create(); + int[] e = F.create(); + int[] f = F.create(); + int[] g = F.create(); + int[] h = F.create(); + + int[] nc, nd, nf, ng; + if (negate) + { + nc = d; nd = c; nf = g; ng = f; + } + else + { + nc = c; nd = d; nf = f; ng = g; + } + + F.apm(p.y, p.x, b, a); + F.apm(q.y, q.x, nd, nc); + F.mul(a, c, a); + F.mul(b, d, b); + F.mul(p.t, q.t, c); + F.mul(c, C_d2, c); + F.mul(p.z, q.z, d); + F.add(d, d, d); + F.apm(b, a, h, e); + F.apm(d, c, ng, nf); + F.carry(ng); + F.mul(e, f, r.x); + F.mul(g, h, r.y); + F.mul(f, g, r.z); + F.mul(e, h, r.t); + } + + protected static void pointAddPrecomp(PointPrecomp p, PointAccum r) + { + int[] a = F.create(); + int[] b = F.create(); + int[] c = F.create(); + int[] e = r.u; + int[] f = F.create(); + int[] g = F.create(); + int[] h = r.v; + + F.apm(r.y, r.x, b, a); + F.mul(a, p.ymx_h, a); + F.mul(b, p.ypx_h, b); + F.mul(r.u, r.v, c); + F.mul(c, p.xyd, c); + F.apm(b, a, h, e); + F.apm(r.z, c, g, f); + F.carry(g); + F.mul(e, f, r.x); + F.mul(g, h, r.y); + F.mul(f, g, r.z); + } + + protected static PointExt pointCopy(PointAccum p) + { + PointExt r = new PointExt(); + F.copy(p.x, 0, r.x, 0); + F.copy(p.y, 0, r.y, 0); + F.copy(p.z, 0, r.z, 0); + F.mul(p.u, p.v, r.t); + return r; + } + + protected static PointExt pointCopy(PointAffine p) + { + PointExt r = new PointExt(); + F.copy(p.x, 0, r.x, 0); + F.copy(p.y, 0, r.y, 0); + pointExtendXY(r); + return r; + } + + protected static PointExt pointCopy(PointExt p) + { + PointExt r = new PointExt(); + pointCopy(p, r); + return r; + } + + protected static void pointCopy(PointAffine p, PointAccum r) + { + F.copy(p.x, 0, r.x, 0); + F.copy(p.y, 0, r.y, 0); + pointExtendXY(r); + } + + protected static void pointCopy(PointExt p, PointExt r) + { + F.copy(p.x, 0, r.x, 0); + F.copy(p.y, 0, r.y, 0); + F.copy(p.z, 0, r.z, 0); + F.copy(p.t, 0, r.t, 0); + } + + protected static void pointDouble(PointAccum r) + { + int[] a = F.create(); + int[] b = F.create(); + int[] c = F.create(); + int[] e = r.u; + int[] f = F.create(); + int[] g = F.create(); + int[] h = r.v; + + F.sqr(r.x, a); + F.sqr(r.y, b); + F.sqr(r.z, c); + F.add(c, c, c); + F.apm(a, b, h, g); + F.add(r.x, r.y, e); + F.sqr(e, e); + F.sub(h, e, e); + F.add(c, g, f); + F.carry(f); + F.mul(e, f, r.x); + F.mul(g, h, r.y); + F.mul(f, g, r.z); + } + + protected static void pointExtendXY(PointAccum p) + { + F.one(p.z); + F.copy(p.x, 0, p.u, 0); + F.copy(p.y, 0, p.v, 0); + } + + protected static void pointExtendXY(PointExt p) + { + F.one(p.z); + F.mul(p.x, p.y, p.t); + } + + protected static void pointLookup(int block, int index, PointPrecomp p) + { +// assert 0 <= block && block < PRECOMP_BLOCKS; +// assert 0 <= index && index < PRECOMP_POINTS; + + int off = block * PRECOMP_POINTS * 3 * F.SIZE; + + for (int i = 0; i < PRECOMP_POINTS; ++i) + { + int cond = ((i ^ index) - 1) >> 31; + F.cmov(cond, precompBase, off, p.ypx_h, 0); off += F.SIZE; + F.cmov(cond, precompBase, off, p.ymx_h, 0); off += F.SIZE; + F.cmov(cond, precompBase, off, p.xyd, 0); off += F.SIZE; + } + } + + protected static void pointLookup(int[] x, int n, int[] table, PointExt r) + { + // TODO This method is currently hardcoded to 4-bit windows and 8 precomputed points + + int w = getWindow4(x, n); + + int sign = (w >>> (4 - 1)) ^ 1; + int abs = (w ^ -sign) & 7; + +// assert sign == 0 || sign == 1; +// assert 0 <= abs && abs < 8; + + for (int i = 0, off = 0; i < 8; ++i) + { + int cond = ((i ^ abs) - 1) >> 31; + F.cmov(cond, table, off, r.x, 0); off += F.SIZE; + F.cmov(cond, table, off, r.y, 0); off += F.SIZE; + F.cmov(cond, table, off, r.z, 0); off += F.SIZE; + F.cmov(cond, table, off, r.t, 0); off += F.SIZE; + } + + F.cnegate(sign, r.x); + F.cnegate(sign, r.t); + } + + protected static void pointLookup(int[] table, int index, PointExt r) + { + int off = F.SIZE * 4 * index; + + F.copy(table, off, r.x, 0); off += F.SIZE; + F.copy(table, off, r.y, 0); off += F.SIZE; + F.copy(table, off, r.z, 0); off += F.SIZE; + F.copy(table, off, r.t, 0); + } + + protected static int[] pointPrecompute(PointAffine p, int count) + { +// assert count > 0; + + PointExt q = pointCopy(p); + PointExt d = pointCopy(q); + pointAdd(q, d); + + int[] table = F.createTable(count * 4); + int off = 0; + + int i = 0; + for (;;) + { + F.copy(q.x, 0, table, off); off += F.SIZE; + F.copy(q.y, 0, table, off); off += F.SIZE; + F.copy(q.z, 0, table, off); off += F.SIZE; + F.copy(q.t, 0, table, off); off += F.SIZE; + + if (++i == count) + { + break; + } + + pointAdd(d, q); + } + + return table; + } + + protected static PointExt[] pointPrecomputeVar(PointExt p, int count) + { +// assert count > 0; + + PointExt d = new PointExt(); + pointAddVar(false, p, p, d); + + PointExt[] table = new PointExt[count]; + table[0] = pointCopy(p); + for (int i = 1; i < count; ++i) + { + pointAddVar(false, table[i - 1], d, table[i] = new PointExt()); + } + return table; + } + + protected static void pointSetNeutral(PointAccum p) + { + F.zero(p.x); + F.one(p.y); + F.one(p.z); + F.zero(p.u); + F.one(p.v); + } + + protected static void pointSetNeutral(PointExt p) + { + F.zero(p.x); + F.one(p.y); + F.one(p.z); + F.zero(p.t); + } + + public static void precompute() + { + synchronized (precompLock) + { + if (precompBase != null) + { + return; + } + + // Precomputed table for the base point in verification ladder + { + PointExt b = new PointExt(); + F.copy(B_x, 0, b.x, 0); + F.copy(B_y, 0, b.y, 0); + pointExtendXY(b); + + precompBaseTable = pointPrecomputeVar(b, 1 << (WNAF_WIDTH_BASE - 2)); + } + + PointAccum p = new PointAccum(); + F.copy(B_x, 0, p.x, 0); + F.copy(B_y, 0, p.y, 0); + pointExtendXY(p); + + precompBase = F.createTable(PRECOMP_BLOCKS * PRECOMP_POINTS * 3); + + int off = 0; + for (int b = 0; b < PRECOMP_BLOCKS; ++b) + { + PointExt[] ds = new PointExt[PRECOMP_TEETH]; + + PointExt sum = new PointExt(); + pointSetNeutral(sum); + + for (int t = 0; t < PRECOMP_TEETH; ++t) + { + PointExt q = pointCopy(p); + pointAddVar(true, sum, q, sum); + pointDouble(p); + + ds[t] = pointCopy(p); + + if (b + t != PRECOMP_BLOCKS + PRECOMP_TEETH - 2) + { + for (int s = 1; s < PRECOMP_SPACING; ++s) + { + pointDouble(p); + } + } + } + + PointExt[] points = new PointExt[PRECOMP_POINTS]; + int k = 0; + points[k++] = sum; + + for (int t = 0; t < (PRECOMP_TEETH - 1); ++t) + { + int size = 1 << t; + for (int j = 0; j < size; ++j, ++k) + { + pointAddVar(false, points[k - size], ds[t], points[k] = new PointExt()); + } + } + +// assert k == PRECOMP_POINTS; + + int[] cs = F.createTable(PRECOMP_POINTS); + + // TODO[ed25519] A single batch inversion across all blocks? + { + int[] u = F.create(); + F.copy(points[0].z, 0, u, 0); + F.copy(u, 0, cs, 0); + + int i = 0; + while (++i < PRECOMP_POINTS) + { + F.mul(u, points[i].z, u); + F.copy(u, 0, cs, i * F.SIZE); + } + + F.add(u, u, u); + F.invVar(u, u); + --i; + + int[] t = F.create(); + + while (i > 0) + { + int j = i--; + F.copy(cs, i * F.SIZE, t, 0); + F.mul(t, u, t); + F.copy(t, 0, cs, j * F.SIZE); + F.mul(u, points[j].z, u); + } + + F.copy(u, 0, cs, 0); + } + + for (int i = 0; i < PRECOMP_POINTS; ++i) + { + PointExt q = points[i]; + + int[] x = F.create(); + int[] y = F.create(); + +// F.add(q.z, q.z, x); +// F.invVar(x, y); + F.copy(cs, i * F.SIZE, y, 0); + + F.mul(q.x, y, x); + F.mul(q.y, y, y); + + PointPrecomp r = new PointPrecomp(); + F.apm(y, x, r.ypx_h, r.ymx_h); + F.mul(x, y, r.xyd); + F.mul(r.xyd, C_d4, r.xyd); + + F.normalize(r.ypx_h); + F.normalize(r.ymx_h); +// F.normalize(r.xyd); + + F.copy(r.ypx_h, 0, precompBase, off); off += F.SIZE; + F.copy(r.ymx_h, 0, precompBase, off); off += F.SIZE; + F.copy(r.xyd, 0, precompBase, off); off += F.SIZE; + } + } + +// assert off == precompBase.length; + } + } + + protected static void pruneScalar(byte[] n, int nOff, byte[] r) + { + System.arraycopy(n, nOff, r, 0, SCALAR_BYTES); + + r[0] &= 0xF8; + r[SCALAR_BYTES - 1] &= 0x7F; + r[SCALAR_BYTES - 1] |= 0x40; + } + + protected static byte[] reduceScalar(byte[] n) + { + long x00 = decode32(n, 0) & M32L; // x00:32/-- + long x01 = (decode24(n, 4) << 4) & M32L; // x01:28/-- + long x02 = decode32(n, 7) & M32L; // x02:32/-- + long x03 = (decode24(n, 11) << 4) & M32L; // x03:28/-- + long x04 = decode32(n, 14) & M32L; // x04:32/-- + long x05 = (decode24(n, 18) << 4) & M32L; // x05:28/-- + long x06 = decode32(n, 21) & M32L; // x06:32/-- + long x07 = (decode24(n, 25) << 4) & M32L; // x07:28/-- + long x08 = decode32(n, 28) & M32L; // x08:32/-- + long x09 = (decode24(n, 32) << 4) & M32L; // x09:28/-- + long x10 = decode32(n, 35) & M32L; // x10:32/-- + long x11 = (decode24(n, 39) << 4) & M32L; // x11:28/-- + long x12 = decode32(n, 42) & M32L; // x12:32/-- + long x13 = (decode24(n, 46) << 4) & M32L; // x13:28/-- + long x14 = decode32(n, 49) & M32L; // x14:32/-- + long x15 = (decode24(n, 53) << 4) & M32L; // x15:28/-- + long x16 = decode32(n, 56) & M32L; // x16:32/-- + long x17 = (decode24(n, 60) << 4) & M32L; // x17:28/-- + long x18 = n[63] & M08L; // x18:08/-- + long t; + +// x18 += (x17 >> 28); x17 &= M28L; + x09 -= x18 * L0; // x09:34/28 + x10 -= x18 * L1; // x10:33/30 + x11 -= x18 * L2; // x11:35/28 + x12 -= x18 * L3; // x12:32/31 + x13 -= x18 * L4; // x13:28/21 + + x17 += (x16 >> 28); x16 &= M28L; // x17:28/--, x16:28/-- + x08 -= x17 * L0; // x08:54/32 + x09 -= x17 * L1; // x09:52/51 + x10 -= x17 * L2; // x10:55/34 + x11 -= x17 * L3; // x11:51/36 + x12 -= x17 * L4; // x12:41/-- + +// x16 += (x15 >> 28); x15 &= M28L; + x07 -= x16 * L0; // x07:54/28 + x08 -= x16 * L1; // x08:54/53 + x09 -= x16 * L2; // x09:55/53 + x10 -= x16 * L3; // x10:55/52 + x11 -= x16 * L4; // x11:51/41 + + x15 += (x14 >> 28); x14 &= M28L; // x15:28/--, x14:28/-- + x06 -= x15 * L0; // x06:54/32 + x07 -= x15 * L1; // x07:54/53 + x08 -= x15 * L2; // x08:56/-- + x09 -= x15 * L3; // x09:55/54 + x10 -= x15 * L4; // x10:55/53 + +// x14 += (x13 >> 28); x13 &= M28L; + x05 -= x14 * L0; // x05:54/28 + x06 -= x14 * L1; // x06:54/53 + x07 -= x14 * L2; // x07:56/-- + x08 -= x14 * L3; // x08:56/51 + x09 -= x14 * L4; // x09:56/-- + + x13 += (x12 >> 28); x12 &= M28L; // x13:28/22, x12:28/-- + x04 -= x13 * L0; // x04:54/49 + x05 -= x13 * L1; // x05:54/53 + x06 -= x13 * L2; // x06:56/-- + x07 -= x13 * L3; // x07:56/52 + x08 -= x13 * L4; // x08:56/52 + + x12 += (x11 >> 28); x11 &= M28L; // x12:28/24, x11:28/-- + x03 -= x12 * L0; // x03:54/49 + x04 -= x12 * L1; // x04:54/51 + x05 -= x12 * L2; // x05:56/-- + x06 -= x12 * L3; // x06:56/52 + x07 -= x12 * L4; // x07:56/53 + + x11 += (x10 >> 28); x10 &= M28L; // x11:29/--, x10:28/-- + x02 -= x11 * L0; // x02:55/32 + x03 -= x11 * L1; // x03:55/-- + x04 -= x11 * L2; // x04:56/55 + x05 -= x11 * L3; // x05:56/52 + x06 -= x11 * L4; // x06:56/53 + + x10 += (x09 >> 28); x09 &= M28L; // x10:29/--, x09:28/-- + x01 -= x10 * L0; // x01:55/28 + x02 -= x10 * L1; // x02:55/54 + x03 -= x10 * L2; // x03:56/55 + x04 -= x10 * L3; // x04:57/-- + x05 -= x10 * L4; // x05:56/53 + + x08 += (x07 >> 28); x07 &= M28L; // x08:56/53, x07:28/-- + x09 += (x08 >> 28); x08 &= M28L; // x09:29/25, x08:28/-- + + t = x08 >>> 27; + x09 += t; // x09:29/26 + + x00 -= x09 * L0; // x00:55/53 + x01 -= x09 * L1; // x01:55/54 + x02 -= x09 * L2; // x02:57/-- + x03 -= x09 * L3; // x03:57/-- + x04 -= x09 * L4; // x04:57/42 + + x01 += (x00 >> 28); x00 &= M28L; + x02 += (x01 >> 28); x01 &= M28L; + x03 += (x02 >> 28); x02 &= M28L; + x04 += (x03 >> 28); x03 &= M28L; + x05 += (x04 >> 28); x04 &= M28L; + x06 += (x05 >> 28); x05 &= M28L; + x07 += (x06 >> 28); x06 &= M28L; + x08 += (x07 >> 28); x07 &= M28L; + x09 = (x08 >> 28); x08 &= M28L; + + x09 -= t; + +// assert x09 == 0L || x09 == -1L; + + x00 += x09 & L0; + x01 += x09 & L1; + x02 += x09 & L2; + x03 += x09 & L3; + x04 += x09 & L4; + + x01 += (x00 >> 28); x00 &= M28L; + x02 += (x01 >> 28); x01 &= M28L; + x03 += (x02 >> 28); x02 &= M28L; + x04 += (x03 >> 28); x03 &= M28L; + x05 += (x04 >> 28); x04 &= M28L; + x06 += (x05 >> 28); x05 &= M28L; + x07 += (x06 >> 28); x06 &= M28L; + x08 += (x07 >> 28); x07 &= M28L; + + byte[] r = new byte[SCALAR_BYTES]; + encode56(x00 | (x01 << 28), r, 0); + encode56(x02 | (x03 << 28), r, 7); + encode56(x04 | (x05 << 28), r, 14); + encode56(x06 | (x07 << 28), r, 21); + encode32((int)x08, r, 28); + return r; + } + + protected static void scalarMult(byte[] k, PointAffine p, PointAccum r) + { + int[] n = new int[SCALAR_INTS]; + decodeScalar(k, 0, n); + +// assert 0 == (n[0] & 7); +// assert 1 == n[SCALAR_INTS - 1] >>> 30; + + Nat.shiftDownBits(SCALAR_INTS, n, 3, 1); + + // Recode the scalar into signed-digit form + { + //int c1 = + Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n); //assert c1 == 0; + //int c2 = + Nat.shiftDownBit(SCALAR_INTS, n, 0); //assert c2 == (1 << 31); + } + +// assert 1 == n[SCALAR_INTS - 1] >>> 28; + + int[] table = pointPrecompute(p, 8); + PointExt q = new PointExt(); + + // Replace first 4 doublings (2^4 * P) with 1 addition (P + 15 * P) + pointCopy(p, r); + pointLookup(table, 7, q); + pointAdd(q, r); + + int w = 62; + for (;;) + { + pointLookup(n, w, table, q); + pointAdd(q, r); + + pointDouble(r); + pointDouble(r); + pointDouble(r); + + if (--w < 0) + { + break; + } + + pointDouble(r); + } + } + + protected static void scalarMultBase(byte[] k, PointAccum r) + { + precompute(); + + int[] n = new int[SCALAR_INTS]; + decodeScalar(k, 0, n); + + // Recode the scalar into signed-digit form, then group comb bits in each block + { + //int c1 = + Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n); //assert c1 == 0; + //int c2 = + Nat.shiftDownBit(SCALAR_INTS, n, 1); //assert c2 == (1 << 31); + + for (int i = 0; i < SCALAR_INTS; ++i) + { + n[i] = Interleave.shuffle2(n[i]); + } + } + + PointPrecomp p = new PointPrecomp(); + + pointSetNeutral(r); + + int cOff = (PRECOMP_SPACING - 1) * PRECOMP_TEETH; + for (;;) + { + for (int b = 0; b < PRECOMP_BLOCKS; ++b) + { + int w = n[b] >>> cOff; + int sign = (w >>> (PRECOMP_TEETH - 1)) & 1; + int abs = (w ^ -sign) & PRECOMP_MASK; + +// assert sign == 0 || sign == 1; +// assert 0 <= abs && abs < PRECOMP_POINTS; + + pointLookup(b, abs, p); + + F.cswap(sign, p.ypx_h, p.ymx_h); + F.cnegate(sign, p.xyd); + + pointAddPrecomp(p, r); + } + + if ((cOff -= PRECOMP_TEETH) < 0) + { + break; + } + + pointDouble(r); + } + } + + protected static void scalarMultBaseEncoded(byte[] k, byte[] r, int rOff) + { + PointAccum p = new PointAccum(); + scalarMultBase(k, p); + if (0 == encodePoint(p, r, rOff)) + { + throw new IllegalStateException(); + } + } + + /** + * NOTE: Only for use by X25519 + */ + public static void scalarMultBaseYZ(X25519.Friend friend, byte[] k, int kOff, int[] y, int[] z) + { + if (null == friend) + { + throw new NullPointerException("This method is only for use by X25519"); + } + + byte[] n = new byte[SCALAR_BYTES]; + pruneScalar(k, kOff, n); + + PointAccum p = new PointAccum(); + scalarMultBase(n, p); + if (0 == checkPoint(p.x, p.y, p.z)) + { + throw new IllegalStateException(); + } + F.copy(p.y, 0, y, 0); + F.copy(p.z, 0, z, 0); + } + + protected static void scalarMultStrausVar(int[] nb, int[] np, PointAffine p, PointAccum r) + { + precompute(); + + final int width = 5; + + byte[] ws_b = getWnafVar(nb, WNAF_WIDTH_BASE); + byte[] ws_p = getWnafVar(np, width); + + PointExt[] tp = pointPrecomputeVar(pointCopy(p), 1 << (width - 2)); + + pointSetNeutral(r); + + for (int bit = 252;;) + { + int wb = ws_b[bit]; + if (wb != 0) + { + int sign = wb >> 31; + int index = (wb ^ sign) >>> 1; + + pointAddVar((sign != 0), precompBaseTable[index], r); + } + + int wp = ws_p[bit]; + if (wp != 0) + { + int sign = wp >> 31; + int index = (wp ^ sign) >>> 1; + + pointAddVar((sign != 0), tp[index], r); + } + + if (--bit < 0) + { + break; + } + + pointDouble(r); + } + } + + public static void sign(byte[] sk, int skOff, byte[] m, int mOff, int mLen, byte[] sig, int sigOff) + { + byte[] ctx = null; + byte phflag = 0x00; + + implSign(sk, skOff, ctx, phflag, m, mOff, mLen, sig, sigOff); + } + + public static void sign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] m, int mOff, int mLen, byte[] sig, int sigOff) + { + byte[] ctx = null; + byte phflag = 0x00; + + implSign(sk, skOff, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff); + } + + public static void sign(byte[] sk, int skOff, byte[] ctx, byte[] m, int mOff, int mLen, byte[] sig, int sigOff) + { + byte phflag = 0x00; + + implSign(sk, skOff, ctx, phflag, m, mOff, mLen, sig, sigOff); + } + + public static void sign(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen, byte[] sig, int sigOff) + { + byte phflag = 0x00; + + implSign(sk, skOff, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff); + } + + public static void signPrehash(byte[] sk, int skOff, byte[] ctx, byte[] ph, int phOff, byte[] sig, int sigOff) + { + byte phflag = 0x01; + + implSign(sk, skOff, ctx, phflag, ph, phOff, PREHASH_SIZE, sig, sigOff); + } + + public static void signPrehash(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, byte[] ph, int phOff, byte[] sig, int sigOff) + { + byte phflag = 0x01; + + implSign(sk, skOff, pk, pkOff, ctx, phflag, ph, phOff, PREHASH_SIZE, sig, sigOff); + } + + public static void signPrehash(byte[] sk, int skOff, byte[] ctx, Digest ph, byte[] sig, int sigOff) + { + byte[] m = new byte[PREHASH_SIZE]; + if (PREHASH_SIZE != ph.doFinal(m, 0)) + { + throw new IllegalArgumentException("ph"); + } + + byte phflag = 0x01; + + implSign(sk, skOff, ctx, phflag, m, 0, m.length, sig, sigOff); + } + + public static void signPrehash(byte[] sk, int skOff, byte[] pk, int pkOff, byte[] ctx, Digest ph, byte[] sig, int sigOff) + { + byte[] m = new byte[PREHASH_SIZE]; + if (PREHASH_SIZE != ph.doFinal(m, 0)) + { + throw new IllegalArgumentException("ph"); + } + + byte phflag = 0x01; + + implSign(sk, skOff, pk, pkOff, ctx, phflag, m, 0, m.length, sig, sigOff); + } + + public static boolean verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] m, int mOff, int mLen) + { + byte[] ctx = null; + byte phflag = 0x00; + + return implVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, mOff, mLen); + } + + public static boolean verify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] m, int mOff, int mLen) + { + byte phflag = 0x00; + + return implVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, mOff, mLen); + } + + public static boolean verifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte[] ph, int phOff) + { + byte phflag = 0x01; + + return implVerify(sig, sigOff, pk, pkOff, ctx, phflag, ph, phOff, PREHASH_SIZE); + } + + public static boolean verifyPrehash(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, Digest ph) + { + byte[] m = new byte[PREHASH_SIZE]; + if (PREHASH_SIZE != ph.doFinal(m, 0)) + { + throw new IllegalArgumentException("ph"); + } + + byte phflag = 0x01; + + return implVerify(sig, sigOff, pk, pkOff, ctx, phflag, m, 0, m.length); + } +} diff --git a/src/main/java/org/qortal/crypto/Crypto.java b/src/main/java/org/qortal/crypto/Crypto.java index 5d91781c..75e5028e 100644 --- a/src/main/java/org/qortal/crypto/Crypto.java +++ b/src/main/java/org/qortal/crypto/Crypto.java @@ -253,6 +253,10 @@ public abstract class Crypto { return false; } + public static byte[] toPublicKey(byte[] privateKey) { + return new Ed25519PrivateKeyParameters(privateKey, 0).generatePublicKey().getEncoded(); + } + public static boolean verify(byte[] publicKey, byte[] signature, byte[] message) { try { return Ed25519.verify(signature, 0, publicKey, 0, message, 0, message.length); @@ -264,16 +268,24 @@ public abstract class Crypto { public static byte[] sign(Ed25519PrivateKeyParameters edPrivateKeyParams, byte[] message) { byte[] signature = new byte[SIGNATURE_LENGTH]; - edPrivateKeyParams.sign(Ed25519.Algorithm.Ed25519, edPrivateKeyParams.generatePublicKey(), null, message, 0, message.length, signature, 0); + edPrivateKeyParams.sign(Ed25519.Algorithm.Ed25519,null, message, 0, message.length, signature, 0); + + return signature; + } + + public static byte[] sign(byte[] privateKey, byte[] message) { + byte[] signature = new byte[SIGNATURE_LENGTH]; + + new Ed25519PrivateKeyParameters(privateKey, 0).sign(Ed25519.Algorithm.Ed25519,null, message, 0, message.length, signature, 0); return signature; } public static byte[] getSharedSecret(byte[] privateKey, byte[] publicKey) { - byte[] x25519PrivateKey = BouncyCastle25519.toX25519PrivateKey(privateKey); + byte[] x25519PrivateKey = Qortal25519Extras.toX25519PrivateKey(privateKey); X25519PrivateKeyParameters xPrivateKeyParams = new X25519PrivateKeyParameters(x25519PrivateKey, 0); - byte[] x25519PublicKey = BouncyCastle25519.toX25519PublicKey(publicKey); + byte[] x25519PublicKey = Qortal25519Extras.toX25519PublicKey(publicKey); X25519PublicKeyParameters xPublicKeyParams = new X25519PublicKeyParameters(x25519PublicKey, 0); byte[] sharedSecret = new byte[SHARED_SECRET_LENGTH]; @@ -281,5 +293,4 @@ public abstract class Crypto { return sharedSecret; } - } diff --git a/src/main/java/org/qortal/crypto/MemoryPoW.java b/src/main/java/org/qortal/crypto/MemoryPoW.java index 01f4f6fd..634b8f9b 100644 --- a/src/main/java/org/qortal/crypto/MemoryPoW.java +++ b/src/main/java/org/qortal/crypto/MemoryPoW.java @@ -1,10 +1,44 @@ package org.qortal.crypto; +import org.qortal.utils.NTP; + import java.nio.ByteBuffer; +import java.util.concurrent.TimeoutException; public class MemoryPoW { + /** + * Compute a MemoryPoW nonce + * + * @param data + * @param workBufferLength + * @param difficulty + * @return + * @throws TimeoutException + */ public static Integer compute2(byte[] data, int workBufferLength, long difficulty) { + try { + return MemoryPoW.compute2(data, workBufferLength, difficulty, null); + + } catch (TimeoutException e) { + // This won't happen, because above timeout is null + return null; + } + } + + /** + * Compute a MemoryPoW nonce, with optional timeout + * + * @param data + * @param workBufferLength + * @param difficulty + * @param timeout maximum number of milliseconds to compute for before giving up,
        or null if no timeout + * @return + * @throws TimeoutException + */ + public static Integer compute2(byte[] data, int workBufferLength, long difficulty, Long timeout) throws TimeoutException { + long startTime = NTP.getTime(); + // Hash data with SHA256 byte[] hash = Crypto.digest(data); @@ -33,6 +67,13 @@ public class MemoryPoW { if (Thread.currentThread().isInterrupted()) return -1; + if (timeout != null) { + long now = NTP.getTime(); + if (now > startTime + timeout) { + throw new TimeoutException("Timeout reached"); + } + } + seed *= seedMultiplier; // per nonce state[0] = longHash[0] ^ seed; @@ -58,6 +99,10 @@ public class MemoryPoW { } public static boolean verify2(byte[] data, int workBufferLength, long difficulty, int nonce) { + return verify2(data, null, workBufferLength, difficulty, nonce); + } + + public static boolean verify2(byte[] data, long[] workBuffer, int workBufferLength, long difficulty, int nonce) { // Hash data with SHA256 byte[] hash = Crypto.digest(data); @@ -70,7 +115,10 @@ public class MemoryPoW { byteBuffer = null; int longBufferLength = workBufferLength / 8; - long[] workBuffer = new long[longBufferLength]; + + if (workBuffer == null) + workBuffer = new long[longBufferLength]; + long[] state = new long[4]; long seed = 8682522807148012L; diff --git a/src/main/java/org/qortal/crypto/Qortal25519Extras.java b/src/main/java/org/qortal/crypto/Qortal25519Extras.java new file mode 100644 index 00000000..42cca93e --- /dev/null +++ b/src/main/java/org/qortal/crypto/Qortal25519Extras.java @@ -0,0 +1,234 @@ +package org.qortal.crypto; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA512Digest; +import org.bouncycastle.math.ec.rfc7748.X25519; +import org.bouncycastle.math.ec.rfc7748.X25519Field; +import org.bouncycastle.math.ec.rfc8032.Ed25519; +import org.bouncycastle.math.raw.Nat256; + +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collection; + +/** + * Additions to BouncyCastle providing: + *

        + *
          + *
        • Ed25519 to X25519 key conversion
        • + *
        • Aggregate public keys
        • + *
        • Aggregate signatures
        • + *
        + */ +public abstract class Qortal25519Extras extends BouncyCastleEd25519 { + + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + public static byte[] toX25519PublicKey(byte[] ed25519PublicKey) { + int[] one = new int[X25519Field.SIZE]; + X25519Field.one(one); + + PointAffine pA = new PointAffine(); + if (!decodePointVar(ed25519PublicKey, 0, true, pA)) + return null; + + int[] y = pA.y; + + int[] oneMinusY = new int[X25519Field.SIZE]; + X25519Field.sub(one, y, oneMinusY); + + int[] onePlusY = new int[X25519Field.SIZE]; + X25519Field.add(one, y, onePlusY); + + int[] oneMinusYInverted = new int[X25519Field.SIZE]; + X25519Field.inv(oneMinusY, oneMinusYInverted); + + int[] u = new int[X25519Field.SIZE]; + X25519Field.mul(onePlusY, oneMinusYInverted, u); + + X25519Field.normalize(u); + + byte[] x25519PublicKey = new byte[X25519.SCALAR_SIZE]; + X25519Field.encode(u, x25519PublicKey, 0); + + return x25519PublicKey; + } + + public static byte[] toX25519PrivateKey(byte[] ed25519PrivateKey) { + Digest d = Ed25519.createPrehash(); + byte[] h = new byte[d.getDigestSize()]; + + d.update(ed25519PrivateKey, 0, ed25519PrivateKey.length); + d.doFinal(h, 0); + + byte[] s = new byte[X25519.SCALAR_SIZE]; + + System.arraycopy(h, 0, s, 0, X25519.SCALAR_SIZE); + s[0] &= 0xF8; + s[X25519.SCALAR_SIZE - 1] &= 0x7F; + s[X25519.SCALAR_SIZE - 1] |= 0x40; + + return s; + } + + // Mostly for test support + public static PointAccum newPointAccum() { + return new PointAccum(); + } + + public static byte[] aggregatePublicKeys(Collection publicKeys) { + PointAccum rAccum = null; + + for (byte[] publicKey : publicKeys) { + PointAffine pA = new PointAffine(); + if (!decodePointVar(publicKey, 0, false, pA)) + // Failed to decode + return null; + + if (rAccum == null) { + rAccum = new PointAccum(); + pointCopy(pA, rAccum); + } else { + pointAdd(pointCopy(pA), rAccum); + } + } + + byte[] publicKey = new byte[SCALAR_BYTES]; + if (0 == encodePoint(rAccum, publicKey, 0)) + // Failed to encode + return null; + + return publicKey; + } + + public static byte[] aggregateSignatures(Collection signatures) { + // Signatures are (R, s) + // R is a point + // s is a scalar + PointAccum rAccum = null; + int[] sAccum = new int[SCALAR_INTS]; + + byte[] rEncoded = new byte[POINT_BYTES]; + int[] sPart = new int[SCALAR_INTS]; + for (byte[] signature : signatures) { + System.arraycopy(signature,0, rEncoded, 0, rEncoded.length); + + PointAffine pA = new PointAffine(); + if (!decodePointVar(rEncoded, 0, false, pA)) + // Failed to decode + return null; + + if (rAccum == null) { + rAccum = new PointAccum(); + pointCopy(pA, rAccum); + + decode32(signature, rEncoded.length, sAccum, 0, SCALAR_INTS); + } else { + pointAdd(pointCopy(pA), rAccum); + + decode32(signature, rEncoded.length, sPart, 0, SCALAR_INTS); + Nat256.addTo(sPart, sAccum); + + // "mod L" on sAccum + if (Nat256.gte(sAccum, L)) + Nat256.subFrom(L, sAccum); + } + } + + byte[] signature = new byte[SIGNATURE_SIZE]; + if (0 == encodePoint(rAccum, signature, 0)) + // Failed to encode + return null; + + for (int i = 0; i < sAccum.length; ++i) { + encode32(sAccum[i], signature, POINT_BYTES + i * 4); + } + + return signature; + } + + public static byte[] signForAggregation(byte[] privateKey, byte[] message) { + // Very similar to BouncyCastle's implementation except we use secure random nonce and different hash + Digest d = new SHA512Digest(); + byte[] h = new byte[d.getDigestSize()]; + + d.reset(); + d.update(privateKey, 0, privateKey.length); + d.doFinal(h, 0); + + byte[] sH = new byte[SCALAR_BYTES]; + pruneScalar(h, 0, sH); + + byte[] publicKey = new byte[SCALAR_BYTES]; + scalarMultBaseEncoded(sH, publicKey, 0); + + byte[] rSeed = new byte[d.getDigestSize()]; + SECURE_RANDOM.nextBytes(rSeed); + + byte[] r = new byte[SCALAR_BYTES]; + pruneScalar(rSeed, 0, r); + + byte[] R = new byte[POINT_BYTES]; + scalarMultBaseEncoded(r, R, 0); + + d.reset(); + d.update(message, 0, message.length); + d.doFinal(h, 0); + byte[] k = reduceScalar(h); + + byte[] s = calculateS(r, k, sH); + + byte[] signature = new byte[SIGNATURE_SIZE]; + System.arraycopy(R, 0, signature, 0, POINT_BYTES); + System.arraycopy(s, 0, signature, POINT_BYTES, SCALAR_BYTES); + + return signature; + } + + public static boolean verifyAggregated(byte[] publicKey, byte[] signature, byte[] message) { + byte[] R = Arrays.copyOfRange(signature, 0, POINT_BYTES); + + byte[] s = Arrays.copyOfRange(signature, POINT_BYTES, POINT_BYTES + SCALAR_BYTES); + + if (!checkPointVar(R)) + // R out of bounds + return false; + + if (!checkScalarVar(s)) + // s out of bounds + return false; + + byte[] S = new byte[POINT_BYTES]; + scalarMultBaseEncoded(s, S, 0); + + PointAffine pA = new PointAffine(); + if (!decodePointVar(publicKey, 0, true, pA)) + // Failed to decode + return false; + + Digest d = new SHA512Digest(); + byte[] h = new byte[d.getDigestSize()]; + + d.update(message, 0, message.length); + d.doFinal(h, 0); + + byte[] k = reduceScalar(h); + + int[] nS = new int[SCALAR_INTS]; + decodeScalar(s, 0, nS); + + int[] nA = new int[SCALAR_INTS]; + decodeScalar(k, 0, nA); + + /*PointAccum*/ + PointAccum pR = new PointAccum(); + scalarMultStrausVar(nS, nA, pA, pR); + + byte[] check = new byte[POINT_BYTES]; + if (0 == encodePoint(pR, check, 0)) + // Failed to encode + return false; + + return Arrays.equals(check, R); + } +} diff --git a/src/main/java/org/qortal/data/account/AccountData.java b/src/main/java/org/qortal/data/account/AccountData.java index 4d662f04..868d1bc1 100644 --- a/src/main/java/org/qortal/data/account/AccountData.java +++ b/src/main/java/org/qortal/data/account/AccountData.java @@ -18,6 +18,7 @@ public class AccountData { protected int level; protected int blocksMinted; protected int blocksMintedAdjustment; + protected int blocksMintedPenalty; // Constructors @@ -25,7 +26,7 @@ public class AccountData { protected AccountData() { } - public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, int level, int blocksMinted, int blocksMintedAdjustment) { + public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, int level, int blocksMinted, int blocksMintedAdjustment, int blocksMintedPenalty) { this.address = address; this.reference = reference; this.publicKey = publicKey; @@ -34,10 +35,11 @@ public class AccountData { this.level = level; this.blocksMinted = blocksMinted; this.blocksMintedAdjustment = blocksMintedAdjustment; + this.blocksMintedPenalty = blocksMintedPenalty; } public AccountData(String address) { - this(address, null, null, Group.NO_GROUP, 0, 0, 0, 0); + this(address, null, null, Group.NO_GROUP, 0, 0, 0, 0, 0); } // Getters/Setters @@ -102,6 +104,14 @@ public class AccountData { this.blocksMintedAdjustment = blocksMintedAdjustment; } + public int getBlocksMintedPenalty() { + return this.blocksMintedPenalty; + } + + public void setBlocksMintedPenalty(int blocksMintedPenalty) { + this.blocksMintedPenalty = blocksMintedPenalty; + } + // Comparison @Override diff --git a/src/main/java/org/qortal/data/account/AccountPenaltyData.java b/src/main/java/org/qortal/data/account/AccountPenaltyData.java new file mode 100644 index 00000000..61947a5f --- /dev/null +++ b/src/main/java/org/qortal/data/account/AccountPenaltyData.java @@ -0,0 +1,52 @@ +package org.qortal.data.account; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +// All properties to be converted to JSON via JAXB +@XmlAccessorType(XmlAccessType.FIELD) +public class AccountPenaltyData { + + // Properties + private String address; + private int blocksMintedPenalty; + + // Constructors + + // necessary for JAXB + protected AccountPenaltyData() { + } + + public AccountPenaltyData(String address, int blocksMintedPenalty) { + this.address = address; + this.blocksMintedPenalty = blocksMintedPenalty; + } + + // Getters/Setters + + public String getAddress() { + return this.address; + } + + public int getBlocksMintedPenalty() { + return this.blocksMintedPenalty; + } + + public String toString() { + return String.format("%s has penalty %d", this.address, this.blocksMintedPenalty); + } + + @Override + public boolean equals(Object b) { + if (!(b instanceof AccountPenaltyData)) + return false; + + return this.getAddress().equals(((AccountPenaltyData) b).getAddress()); + } + + @Override + public int hashCode() { + return address.hashCode(); + } + +} diff --git a/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceMetadata.java b/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceMetadata.java index 75b5a4d8..497e214f 100644 --- a/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceMetadata.java +++ b/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceMetadata.java @@ -15,19 +15,24 @@ public class ArbitraryResourceMetadata { private List tags; private Category category; private String categoryName; + private List files; public ArbitraryResourceMetadata() { } - public ArbitraryResourceMetadata(String title, String description, List tags, Category category) { + public ArbitraryResourceMetadata(String title, String description, List tags, Category category, List files) { this.title = title; this.description = description; this.tags = tags; this.category = category; - this.categoryName = category.getName(); + this.files = files; + + if (category != null) { + this.categoryName = category.getName(); + } } - public static ArbitraryResourceMetadata fromTransactionMetadata(ArbitraryDataTransactionMetadata transactionMetadata) { + public static ArbitraryResourceMetadata fromTransactionMetadata(ArbitraryDataTransactionMetadata transactionMetadata, boolean includeFileList) { if (transactionMetadata == null) { return null; } @@ -36,10 +41,20 @@ public class ArbitraryResourceMetadata { List tags = transactionMetadata.getTags(); Category category = transactionMetadata.getCategory(); - if (title == null && description == null && tags == null && category == null) { + // We don't always want to include the file list as it can be too verbose + List files = null; + if (includeFileList) { + files = transactionMetadata.getFiles(); + } + + if (title == null && description == null && tags == null && category == null && files == null) { return null; } - return new ArbitraryResourceMetadata(title, description, tags, category); + return new ArbitraryResourceMetadata(title, description, tags, category, files); + } + + public List getFiles() { + return this.files; } } diff --git a/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceStatus.java b/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceStatus.java index 5e6ac055..b1fbbd3c 100644 --- a/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceStatus.java +++ b/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceStatus.java @@ -26,6 +26,7 @@ public class ArbitraryResourceStatus { } } + private Status status; private String id; private String title; private String description; @@ -37,6 +38,7 @@ public class ArbitraryResourceStatus { } public ArbitraryResourceStatus(Status status, Integer localChunkCount, Integer totalChunkCount) { + this.status = status; this.id = status.toString(); this.title = status.title; this.description = status.description; @@ -47,4 +49,20 @@ public class ArbitraryResourceStatus { public ArbitraryResourceStatus(Status status) { this(status, null, null); } + + public Status getStatus() { + return this.status; + } + + public String getTitle() { + return this.title; + } + + public Integer getLocalChunkCount() { + return this.localChunkCount; + } + + public Integer getTotalChunkCount() { + return this.totalChunkCount; + } } diff --git a/src/main/java/org/qortal/data/block/BlockData.java b/src/main/java/org/qortal/data/block/BlockData.java index 61d1a7fb..763bca45 100644 --- a/src/main/java/org/qortal/data/block/BlockData.java +++ b/src/main/java/org/qortal/data/block/BlockData.java @@ -211,6 +211,14 @@ public class BlockData implements Serializable { this.onlineAccountsSignatures = onlineAccountsSignatures; } + public int getOnlineAccountsSignaturesCount() { + if (this.onlineAccountsSignatures != null && this.onlineAccountsSignatures.length > 0) { + // Blocks use a single online accounts signature, so there is no need for this to be dynamic + return 1; + } + return 0; + } + public boolean isTrimmed() { long onlineAccountSignaturesTrimmedTimestamp = NTP.getTime() - BlockChain.getInstance().getOnlineAccountSignaturesMaxLifetime(); long currentTrimmableTimestamp = NTP.getTime() - Settings.getInstance().getAtStatesMaxLifetime(); diff --git a/src/main/java/org/qortal/data/block/BlockSummaryData.java b/src/main/java/org/qortal/data/block/BlockSummaryData.java index 2167f0f0..57e29d0d 100644 --- a/src/main/java/org/qortal/data/block/BlockSummaryData.java +++ b/src/main/java/org/qortal/data/block/BlockSummaryData.java @@ -11,11 +11,12 @@ public class BlockSummaryData { private int height; private byte[] signature; private byte[] minterPublicKey; - private int onlineAccountsCount; // Optional, set during construction + private Integer onlineAccountsCount; private Long timestamp; private Integer transactionCount; + private byte[] reference; // Optional, set after construction private Integer minterLevel; @@ -25,6 +26,15 @@ public class BlockSummaryData { protected BlockSummaryData() { } + /** Constructor typically populated with fields from HeightV2Message */ + public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, long timestamp) { + this.height = height; + this.signature = signature; + this.minterPublicKey = minterPublicKey; + this.timestamp = timestamp; + } + + /** Constructor typically populated with fields from BlockSummariesMessage */ public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, int onlineAccountsCount) { this.height = height; this.signature = signature; @@ -32,13 +42,16 @@ public class BlockSummaryData { this.onlineAccountsCount = onlineAccountsCount; } - public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, int onlineAccountsCount, long timestamp, int transactionCount) { + /** Constructor typically populated with fields from BlockSummariesV2Message */ + public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, Integer onlineAccountsCount, + Long timestamp, Integer transactionCount, byte[] reference) { this.height = height; this.signature = signature; this.minterPublicKey = minterPublicKey; this.onlineAccountsCount = onlineAccountsCount; this.timestamp = timestamp; this.transactionCount = transactionCount; + this.reference = reference; } public BlockSummaryData(BlockData blockData) { @@ -49,6 +62,7 @@ public class BlockSummaryData { this.timestamp = blockData.getTimestamp(); this.transactionCount = blockData.getTransactionCount(); + this.reference = blockData.getReference(); } // Getters / setters @@ -65,7 +79,7 @@ public class BlockSummaryData { return this.minterPublicKey; } - public int getOnlineAccountsCount() { + public Integer getOnlineAccountsCount() { return this.onlineAccountsCount; } @@ -77,6 +91,10 @@ public class BlockSummaryData { return this.transactionCount; } + public byte[] getReference() { + return this.reference; + } + public Integer getMinterLevel() { return this.minterLevel; } diff --git a/src/main/java/org/qortal/data/block/CommonBlockData.java b/src/main/java/org/qortal/data/block/CommonBlockData.java index dd502df7..37e9649b 100644 --- a/src/main/java/org/qortal/data/block/CommonBlockData.java +++ b/src/main/java/org/qortal/data/block/CommonBlockData.java @@ -1,7 +1,5 @@ package org.qortal.data.block; -import org.qortal.data.network.PeerChainTipData; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import java.math.BigInteger; @@ -14,14 +12,14 @@ public class CommonBlockData { private BlockSummaryData commonBlockSummary = null; private List blockSummariesAfterCommonBlock = null; private BigInteger chainWeight = null; - private PeerChainTipData chainTipData = null; + private BlockSummaryData chainTipData = null; // Constructors protected CommonBlockData() { } - public CommonBlockData(BlockSummaryData commonBlockSummary, PeerChainTipData chainTipData) { + public CommonBlockData(BlockSummaryData commonBlockSummary, BlockSummaryData chainTipData) { this.commonBlockSummary = commonBlockSummary; this.chainTipData = chainTipData; } @@ -49,7 +47,7 @@ public class CommonBlockData { this.chainWeight = chainWeight; } - public PeerChainTipData getChainTipData() { + public BlockSummaryData getChainTipData() { return this.chainTipData; } diff --git a/src/main/java/org/qortal/data/chat/ChatMessage.java b/src/main/java/org/qortal/data/chat/ChatMessage.java index 26df1da4..5d16bb7c 100644 --- a/src/main/java/org/qortal/data/chat/ChatMessage.java +++ b/src/main/java/org/qortal/data/chat/ChatMessage.java @@ -27,6 +27,8 @@ public class ChatMessage { private String recipientName; + private byte[] chatReference; + private byte[] data; private boolean isText; @@ -42,8 +44,8 @@ public class ChatMessage { // For repository use public ChatMessage(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String sender, - String senderName, String recipient, String recipientName, byte[] data, boolean isText, - boolean isEncrypted, byte[] signature) { + String senderName, String recipient, String recipientName, byte[] chatReference, byte[] data, + boolean isText, boolean isEncrypted, byte[] signature) { this.timestamp = timestamp; this.txGroupId = txGroupId; this.reference = reference; @@ -52,6 +54,7 @@ public class ChatMessage { this.senderName = senderName; this.recipient = recipient; this.recipientName = recipientName; + this.chatReference = chatReference; this.data = data; this.isText = isText; this.isEncrypted = isEncrypted; @@ -90,6 +93,10 @@ public class ChatMessage { return this.recipientName; } + public byte[] getChatReference() { + return this.chatReference; + } + public byte[] getData() { return this.data; } diff --git a/src/main/java/org/qortal/data/network/OnlineAccountData.java b/src/main/java/org/qortal/data/network/OnlineAccountData.java index 15792307..bd4842db 100644 --- a/src/main/java/org/qortal/data/network/OnlineAccountData.java +++ b/src/main/java/org/qortal/data/network/OnlineAccountData.java @@ -5,6 +5,7 @@ import java.util.Arrays; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; import org.qortal.account.PublicKeyAccount; @@ -15,6 +16,10 @@ public class OnlineAccountData { protected long timestamp; protected byte[] signature; protected byte[] publicKey; + protected Integer nonce; + + @XmlTransient + private int hash; // Constructors @@ -22,10 +27,15 @@ public class OnlineAccountData { protected OnlineAccountData() { } - public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey) { + public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey, Integer nonce) { this.timestamp = timestamp; this.signature = signature; this.publicKey = publicKey; + this.nonce = nonce; + } + + public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey) { + this(timestamp, signature, publicKey, null); } public long getTimestamp() { @@ -40,6 +50,10 @@ public class OnlineAccountData { return this.publicKey; } + public Integer getNonce() { + return this.nonce; + } + // For JAXB @XmlElement(name = "address") protected String getAddress() { @@ -62,20 +76,23 @@ public class OnlineAccountData { if (otherOnlineAccountData.timestamp != this.timestamp) return false; - // Signature more likely to be unique than public key - if (!Arrays.equals(otherOnlineAccountData.signature, this.signature)) - return false; - if (!Arrays.equals(otherOnlineAccountData.publicKey, this.publicKey)) return false; + // We don't compare signature because it's not our remit to verify and newer aggregate signatures use random nonces + return true; } @Override public int hashCode() { - // Pretty lazy implementation - return (int) this.timestamp; + int h = this.hash; + if (h == 0) { + this.hash = h = Long.hashCode(this.timestamp) + ^ Arrays.hashCode(this.publicKey); + // We don't use signature because newer aggregate signatures use random nonces + } + return h; } } diff --git a/src/main/java/org/qortal/data/network/PeerChainTipData.java b/src/main/java/org/qortal/data/network/PeerChainTipData.java deleted file mode 100644 index d8dbbad4..00000000 --- a/src/main/java/org/qortal/data/network/PeerChainTipData.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.qortal.data.network; - -public class PeerChainTipData { - - /** Latest block height as reported by peer. */ - private Integer lastHeight; - /** Latest block signature as reported by peer. */ - private byte[] lastBlockSignature; - /** Latest block timestamp as reported by peer. */ - private Long lastBlockTimestamp; - /** Latest block minter public key as reported by peer. */ - private byte[] lastBlockMinter; - - public PeerChainTipData(Integer lastHeight, byte[] lastBlockSignature, Long lastBlockTimestamp, byte[] lastBlockMinter) { - this.lastHeight = lastHeight; - this.lastBlockSignature = lastBlockSignature; - this.lastBlockTimestamp = lastBlockTimestamp; - this.lastBlockMinter = lastBlockMinter; - } - - public Integer getLastHeight() { - return this.lastHeight; - } - - public byte[] getLastBlockSignature() { - return this.lastBlockSignature; - } - - public Long getLastBlockTimestamp() { - return this.lastBlockTimestamp; - } - - public byte[] getLastBlockMinter() { - return this.lastBlockMinter; - } - -} diff --git a/src/main/java/org/qortal/data/network/PeerData.java b/src/main/java/org/qortal/data/network/PeerData.java index 09982c00..471685dd 100644 --- a/src/main/java/org/qortal/data/network/PeerData.java +++ b/src/main/java/org/qortal/data/network/PeerData.java @@ -28,6 +28,9 @@ public class PeerData { private Long addedWhen; private String addedBy; + /** The number of consecutive times we failed to sync with this peer */ + private int failedSyncCount = 0; + // Constructors // necessary for JAXB serialization @@ -92,6 +95,18 @@ public class PeerData { return this.addedBy; } + public int getFailedSyncCount() { + return this.failedSyncCount; + } + + public void setFailedSyncCount(int failedSyncCount) { + this.failedSyncCount = failedSyncCount; + } + + public void incrementFailedSyncCount() { + this.failedSyncCount++; + } + // Pretty peerAddress getter for JAXB @XmlElement(name = "address") protected String getPrettyAddress() { diff --git a/src/main/java/org/qortal/data/transaction/CancelSellNameTransactionData.java b/src/main/java/org/qortal/data/transaction/CancelSellNameTransactionData.java index ff3d0a08..14677daf 100644 --- a/src/main/java/org/qortal/data/transaction/CancelSellNameTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/CancelSellNameTransactionData.java @@ -3,6 +3,7 @@ package org.qortal.data.transaction; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlTransient; import org.qortal.transaction.Transaction.TransactionType; @@ -19,6 +20,11 @@ public class CancelSellNameTransactionData extends TransactionData { @Schema(description = "which name to cancel selling", example = "my-name") private String name; + // For internal use when orphaning + @XmlTransient + @Schema(hidden = true) + private Long salePrice; + // Constructors // For JAXB @@ -30,11 +36,17 @@ public class CancelSellNameTransactionData extends TransactionData { this.creatorPublicKey = this.ownerPublicKey; } - public CancelSellNameTransactionData(BaseTransactionData baseTransactionData, String name) { + public CancelSellNameTransactionData(BaseTransactionData baseTransactionData, String name, Long salePrice) { super(TransactionType.CANCEL_SELL_NAME, baseTransactionData); this.ownerPublicKey = baseTransactionData.creatorPublicKey; this.name = name; + this.salePrice = salePrice; + } + + /** From network/API */ + public CancelSellNameTransactionData(BaseTransactionData baseTransactionData, String name) { + this(baseTransactionData, name, null); } // Getters / setters @@ -47,4 +59,12 @@ public class CancelSellNameTransactionData extends TransactionData { return this.name; } + public Long getSalePrice() { + return this.salePrice; + } + + public void setSalePrice(Long salePrice) { + this.salePrice = salePrice; + } + } diff --git a/src/main/java/org/qortal/data/transaction/ChatTransactionData.java b/src/main/java/org/qortal/data/transaction/ChatTransactionData.java index 36ce6124..5a6adf7f 100644 --- a/src/main/java/org/qortal/data/transaction/ChatTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/ChatTransactionData.java @@ -26,6 +26,8 @@ public class ChatTransactionData extends TransactionData { private String recipient; // can be null + private byte[] chatReference; // can be null + @Schema(description = "raw message data, possibly UTF8 text", example = "2yGEbwRFyhPZZckKA") private byte[] data; @@ -44,13 +46,14 @@ public class ChatTransactionData extends TransactionData { } public ChatTransactionData(BaseTransactionData baseTransactionData, - String sender, int nonce, String recipient, byte[] data, boolean isText, boolean isEncrypted) { + String sender, int nonce, String recipient, byte[] chatReference, byte[] data, boolean isText, boolean isEncrypted) { super(TransactionType.CHAT, baseTransactionData); this.senderPublicKey = baseTransactionData.creatorPublicKey; this.sender = sender; this.nonce = nonce; this.recipient = recipient; + this.chatReference = chatReference; this.data = data; this.isText = isText; this.isEncrypted = isEncrypted; @@ -78,6 +81,14 @@ public class ChatTransactionData extends TransactionData { return this.recipient; } + public byte[] getChatReference() { + return this.chatReference; + } + + public void setChatReference(byte[] chatReference) { + this.chatReference = chatReference; + } + public byte[] getData() { return this.data; } diff --git a/src/main/java/org/qortal/data/transaction/DeployAtTransactionData.java b/src/main/java/org/qortal/data/transaction/DeployAtTransactionData.java index 7a2ebdab..fed69cd5 100644 --- a/src/main/java/org/qortal/data/transaction/DeployAtTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/DeployAtTransactionData.java @@ -2,6 +2,7 @@ package org.qortal.data.transaction; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.qortal.transaction.Transaction.TransactionType; @@ -90,4 +91,17 @@ public class DeployAtTransactionData extends TransactionData { this.aTAddress = AtAddress; } + // Re-expose creatorPublicKey for this transaction type for JAXB + @XmlElement(name = "creatorPublicKey") + @Schema(name = "creatorPublicKey", description = "AT creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") + public byte[] getAtCreatorPublicKey() { + return this.creatorPublicKey; + } + + @XmlElement(name = "creatorPublicKey") + @Schema(name = "creatorPublicKey", description = "AT creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") + public void setAtCreatorPublicKey(byte[] creatorPublicKey) { + this.creatorPublicKey = creatorPublicKey; + } + } diff --git a/src/main/java/org/qortal/data/transaction/TransactionData.java b/src/main/java/org/qortal/data/transaction/TransactionData.java index 060901f2..ec1139f4 100644 --- a/src/main/java/org/qortal/data/transaction/TransactionData.java +++ b/src/main/java/org/qortal/data/transaction/TransactionData.java @@ -128,6 +128,10 @@ public abstract class TransactionData { return this.txGroupId; } + public void setTxGroupId(int txGroupId) { + this.txGroupId = txGroupId; + } + public byte[] getReference() { return this.reference; } diff --git a/src/main/java/org/qortal/group/Group.java b/src/main/java/org/qortal/group/Group.java index 1dbb18b0..465743a9 100644 --- a/src/main/java/org/qortal/group/Group.java +++ b/src/main/java/org/qortal/group/Group.java @@ -80,6 +80,9 @@ public class Group { // Useful constants public static final int NO_GROUP = 0; + // Null owner address corresponds with public key "11111111111111111111111111111111" + public static String NULL_OWNER_ADDRESS = "QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG"; + public static final int MIN_NAME_SIZE = 3; public static final int MAX_NAME_SIZE = 32; public static final int MAX_DESCRIPTION_SIZE = 128; diff --git a/src/main/java/org/qortal/naming/Name.java b/src/main/java/org/qortal/naming/Name.java index 97fe8bbb..ecf826a5 100644 --- a/src/main/java/org/qortal/naming/Name.java +++ b/src/main/java/org/qortal/naming/Name.java @@ -180,8 +180,12 @@ public class Name { } public void cancelSell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException { - // Mark not for-sale but leave price in case we want to orphan + // Update previous sale price in transaction data + cancelSellNameTransactionData.setSalePrice(this.nameData.getSalePrice()); + + // Mark not for-sale this.nameData.setIsForSale(false); + this.nameData.setSalePrice(null); // Save sale info into repository this.repository.getNameRepository().save(this.nameData); @@ -190,6 +194,7 @@ public class Name { public void uncancelSell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException { // Mark as for-sale using existing price this.nameData.setIsForSale(true); + this.nameData.setSalePrice(cancelSellNameTransactionData.getSalePrice()); // Save no-sale info into repository this.repository.getNameRepository().save(this.nameData); diff --git a/src/main/java/org/qortal/network/Handshake.java b/src/main/java/org/qortal/network/Handshake.java index 22354cc4..47752767 100644 --- a/src/main/java/org/qortal/network/Handshake.java +++ b/src/main/java/org/qortal/network/Handshake.java @@ -265,7 +265,7 @@ public enum Handshake { private static final long PEER_VERSION_131 = 0x0100030001L; /** Minimum peer version that we are allowed to communicate with */ - private static final String MIN_PEER_VERSION = "3.1.0"; + private static final String MIN_PEER_VERSION = "3.8.2"; private static final int POW_BUFFER_SIZE_PRE_131 = 8 * 1024 * 1024; // bytes private static final int POW_DIFFICULTY_PRE_131 = 8; // leading zero bits diff --git a/src/main/java/org/qortal/network/Network.java b/src/main/java/org/qortal/network/Network.java index 6bc58bb4..f8f73c2a 100644 --- a/src/main/java/org/qortal/network/Network.java +++ b/src/main/java/org/qortal/network/Network.java @@ -11,6 +11,7 @@ import org.qortal.controller.arbitrary.ArbitraryDataFileListManager; import org.qortal.controller.arbitrary.ArbitraryDataManager; import org.qortal.crypto.Crypto; import org.qortal.data.block.BlockData; +import org.qortal.data.block.BlockSummaryData; import org.qortal.data.network.PeerData; import org.qortal.data.transaction.TransactionData; import org.qortal.network.message.*; @@ -90,6 +91,8 @@ public class Network { private static final long DISCONNECTION_CHECK_INTERVAL = 10 * 1000L; // milliseconds + private static final int BROADCAST_CHAIN_TIP_DEPTH = 7; // Just enough to fill a SINGLE TCP packet (~1440 bytes) + // Generate our node keys / ID private final Ed25519PrivateKeyParameters edPrivateKeyParams = new Ed25519PrivateKeyParameters(new SecureRandom()); private final Ed25519PublicKeyParameters edPublicKeyParams = edPrivateKeyParams.generatePublicKey(); @@ -336,7 +339,7 @@ public class Network { try { if (!isConnected) { // Add this signature to the list of pending requests for this peer - LOGGER.info("Making connection to peer {} to request files for signature {}...", peerAddressString, Base58.encode(signature)); + LOGGER.debug("Making connection to peer {} to request files for signature {}...", peerAddressString, Base58.encode(signature)); Peer peer = new Peer(peerData); peer.setIsDataPeer(true); peer.addPendingSignatureRequest(signature); @@ -469,6 +472,8 @@ public class Network { class NetworkProcessor extends ExecuteProduceConsume { + private final Logger LOGGER = LogManager.getLogger(NetworkProcessor.class); + private final AtomicLong nextConnectTaskTimestamp = new AtomicLong(0L); // ms - try first connect once NTP syncs private final AtomicLong nextBroadcastTimestamp = new AtomicLong(0L); // ms - try first broadcast once NTP syncs @@ -1085,10 +1090,16 @@ public class Network { if (peer.isOutbound()) { if (!Settings.getInstance().isLite()) { - // Send our height - Message heightMessage = buildHeightMessage(peer, Controller.getInstance().getChainTip()); - if (!peer.sendMessage(heightMessage)) { - peer.disconnect("failed to send height/info"); + // Send our height / chain tip info + Message message = this.buildHeightOrChainTipInfo(peer); + + if (message == null) { + peer.disconnect("Couldn't build our chain tip info"); + return; + } + + if (!peer.sendMessage(message)) { + peer.disconnect("failed to send height / chain tip info"); return; } } @@ -1162,10 +1173,47 @@ public class Network { return new PeersV2Message(peerAddresses); } - public Message buildHeightMessage(Peer peer, BlockData blockData) { - // HEIGHT_V2 contains way more useful info - return new HeightV2Message(blockData.getHeight(), blockData.getSignature(), - blockData.getTimestamp(), blockData.getMinterPublicKey()); + /** Builds either (legacy) HeightV2Message or (newer) BlockSummariesV2Message, depending on peer version. + * + * @return Message, or null if DataException was thrown. + */ + public Message buildHeightOrChainTipInfo(Peer peer) { + if (peer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION) { + int latestHeight = Controller.getInstance().getChainHeight(); + + try (final Repository repository = RepositoryManager.getRepository()) { + List latestBlockSummaries = repository.getBlockRepository().getBlockSummaries(latestHeight - BROADCAST_CHAIN_TIP_DEPTH, latestHeight); + return new BlockSummariesV2Message(latestBlockSummaries); + } catch (DataException e) { + return null; + } + } else { + // For older peers + BlockData latestBlockData = Controller.getInstance().getChainTip(); + return new HeightV2Message(latestBlockData.getHeight(), latestBlockData.getSignature(), + latestBlockData.getTimestamp(), latestBlockData.getMinterPublicKey()); + } + } + + public void broadcastOurChain() { + BlockData latestBlockData = Controller.getInstance().getChainTip(); + int latestHeight = latestBlockData.getHeight(); + + try (final Repository repository = RepositoryManager.getRepository()) { + List latestBlockSummaries = repository.getBlockRepository().getBlockSummaries(latestHeight - BROADCAST_CHAIN_TIP_DEPTH, latestHeight); + Message latestBlockSummariesMessage = new BlockSummariesV2Message(latestBlockSummaries); + + // For older peers + Message heightMessage = new HeightV2Message(latestBlockData.getHeight(), latestBlockData.getSignature(), + latestBlockData.getTimestamp(), latestBlockData.getMinterPublicKey()); + + Network.getInstance().broadcast(broadcastPeer -> broadcastPeer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION + ? latestBlockSummariesMessage + : heightMessage + ); + } catch (DataException e) { + LOGGER.warn("Couldn't broadcast our chain tip info", e); + } } public Message buildNewTransactionMessage(Peer peer, TransactionData transactionData) { diff --git a/src/main/java/org/qortal/network/Peer.java b/src/main/java/org/qortal/network/Peer.java index f99a94b1..4c05d5b9 100644 --- a/src/main/java/org/qortal/network/Peer.java +++ b/src/main/java/org/qortal/network/Peer.java @@ -6,8 +6,8 @@ import com.google.common.net.InetAddresses; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.controller.Controller; +import org.qortal.data.block.BlockSummaryData; import org.qortal.data.block.CommonBlockData; -import org.qortal.data.network.PeerChainTipData; import org.qortal.data.network.PeerData; import org.qortal.network.message.ChallengeMessage; import org.qortal.network.message.Message; @@ -27,6 +27,8 @@ import java.nio.channels.SocketChannel; import java.security.SecureRandom; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.LongAccumulator; +import java.util.concurrent.atomic.LongAdder; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -146,13 +148,28 @@ public class Peer { /** * Latest block info as reported by peer. */ - private PeerChainTipData peersChainTipData; + private List peersChainTipData = Collections.emptyList(); /** * Our common block with this peer */ private CommonBlockData commonBlockData; + /** + * Last time we detected this peer as TOO_DIVERGENT + */ + private Long lastTooDivergentTime; + + // Message stats + + private static class MessageStats { + public final LongAdder count = new LongAdder(); + public final LongAdder totalBytes = new LongAdder(); + } + + private final Map receivedMessageStats = new ConcurrentHashMap<>(); + private final Map sentMessageStats = new ConcurrentHashMap<>(); + // Constructors /** @@ -341,28 +358,42 @@ public class Peer { } } - public PeerChainTipData getChainTipData() { - synchronized (this.peerInfoLock) { - return this.peersChainTipData; - } + public BlockSummaryData getChainTipData() { + List chainTipSummaries = this.peersChainTipData; + + if (chainTipSummaries.isEmpty()) + return null; + + // Return last entry, which should have greatest height + return chainTipSummaries.get(chainTipSummaries.size() - 1); } - public void setChainTipData(PeerChainTipData chainTipData) { - synchronized (this.peerInfoLock) { - this.peersChainTipData = chainTipData; - } + public void setChainTipData(BlockSummaryData chainTipData) { + this.peersChainTipData = Collections.singletonList(chainTipData); + } + + public List getChainTipSummaries() { + return this.peersChainTipData; + } + + public void setChainTipSummaries(List chainTipSummaries) { + this.peersChainTipData = List.copyOf(chainTipSummaries); } public CommonBlockData getCommonBlockData() { - synchronized (this.peerInfoLock) { - return this.commonBlockData; - } + return this.commonBlockData; } public void setCommonBlockData(CommonBlockData commonBlockData) { - synchronized (this.peerInfoLock) { - this.commonBlockData = commonBlockData; - } + this.commonBlockData = commonBlockData; + } + + public Long getLastTooDivergentTime() { + return this.lastTooDivergentTime; + } + + public void setLastTooDivergentTime(Long lastTooDivergentTime) { + this.lastTooDivergentTime = lastTooDivergentTime; } public boolean isSyncInProgress() { @@ -542,11 +573,18 @@ public class Peer { // Tidy up buffers: this.byteBuffer.flip(); // Read-only, flipped buffer's position will be after end of message, so copy that + long messageByteSize = readOnlyBuffer.position(); this.byteBuffer.position(readOnlyBuffer.position()); // Copy bytes after read message to front of buffer, // adjusting position accordingly, reset limit to capacity this.byteBuffer.compact(); + // Record message stats + MessageStats messageStats = this.receivedMessageStats.computeIfAbsent(message.getType(), k -> new MessageStats()); + // Ideally these two operations would be atomic, we could pack 'count' in top X bits of the 64-bit long, but meh + messageStats.count.increment(); + messageStats.totalBytes.add(messageByteSize); + // Unsupported message type? Discard with no further processing if (message.getType() == MessageType.UNSUPPORTED) continue; @@ -609,6 +647,12 @@ public class Peer { LOGGER.trace("[{}] Sending {} message with ID {} to peer {}", this.peerConnectionId, this.outputMessageType, this.outputMessageId, this); + + // Record message stats + MessageStats messageStats = this.sentMessageStats.computeIfAbsent(message.getType(), k -> new MessageStats()); + // Ideally these two operations would be atomic, we could pack 'count' in top X bits of the 64-bit long, but meh + messageStats.count.increment(); + messageStats.totalBytes.add(this.outputBuffer.limit()); } catch (MessageException e) { // Something went wrong converting message to bytes, so discard but allow another round LOGGER.warn("[{}] Failed to send {} message with ID {} to peer {}: {}", this.peerConnectionId, @@ -799,8 +843,11 @@ public class Peer { } public void shutdown() { + boolean logStats = false; + if (!isStopping) { LOGGER.debug("[{}] Shutting down peer {}", this.peerConnectionId, this); + logStats = true; } isStopping = true; @@ -812,8 +859,34 @@ public class Peer { LOGGER.debug("[{}] IOException while trying to close peer {}", this.peerConnectionId, this); } } + + if (logStats && this.receivedMessageStats.size() > 0) { + StringBuilder statsBuilder = new StringBuilder(1024); + statsBuilder.append("peer ").append(this).append(" message stats:\n=received="); + appendMessageStats(statsBuilder, this.receivedMessageStats); + statsBuilder.append("\n=sent="); + appendMessageStats(statsBuilder, this.sentMessageStats); + + LOGGER.debug(statsBuilder.toString()); + } } + private static void appendMessageStats(StringBuilder statsBuilder, Map messageStats) { + if (messageStats.isEmpty()) { + statsBuilder.append("\n none"); + return; + } + + messageStats.keySet().stream() + .sorted(Comparator.comparing(MessageType::name)) + .forEach(messageType -> { + MessageStats stats = messageStats.get(messageType); + + statsBuilder.append("\n ").append(messageType.name()) + .append(": count=").append(stats.count.sum()) + .append(", total bytes=").append(stats.totalBytes.sum()); + }); + } // Minimum version @@ -850,20 +923,22 @@ public class Peer { // Common block data public boolean canUseCachedCommonBlockData() { - PeerChainTipData peerChainTipData = this.getChainTipData(); - CommonBlockData commonBlockData = this.getCommonBlockData(); + BlockSummaryData peerChainTipData = this.getChainTipData(); + if (peerChainTipData == null || peerChainTipData.getSignature() == null) + return false; - if (peerChainTipData != null && commonBlockData != null) { - PeerChainTipData commonBlockChainTipData = commonBlockData.getChainTipData(); - if (peerChainTipData.getLastBlockSignature() != null && commonBlockChainTipData != null - && commonBlockChainTipData.getLastBlockSignature() != null) { - if (Arrays.equals(peerChainTipData.getLastBlockSignature(), - commonBlockChainTipData.getLastBlockSignature())) { - return true; - } - } - } - return false; + CommonBlockData commonBlockData = this.getCommonBlockData(); + if (commonBlockData == null) + return false; + + BlockSummaryData commonBlockChainTipData = commonBlockData.getChainTipData(); + if (commonBlockChainTipData == null || commonBlockChainTipData.getSignature() == null) + return false; + + if (!Arrays.equals(peerChainTipData.getSignature(), commonBlockChainTipData.getSignature())) + return false; + + return true; } diff --git a/src/main/java/org/qortal/network/message/AccountMessage.java b/src/main/java/org/qortal/network/message/AccountMessage.java index d22ef879..453862b0 100644 --- a/src/main/java/org/qortal/network/message/AccountMessage.java +++ b/src/main/java/org/qortal/network/message/AccountMessage.java @@ -41,6 +41,8 @@ public class AccountMessage extends Message { bytes.write(Ints.toByteArray(accountData.getBlocksMintedAdjustment())); + bytes.write(Ints.toByteArray(accountData.getBlocksMintedPenalty())); + } catch (IOException e) { throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); } @@ -80,7 +82,9 @@ public class AccountMessage extends Message { int blocksMintedAdjustment = byteBuffer.getInt(); - AccountData accountData = new AccountData(address, reference, publicKey, defaultGroupId, flags, level, blocksMinted, blocksMintedAdjustment); + int blocksMintedPenalty = byteBuffer.getInt(); + + AccountData accountData = new AccountData(address, reference, publicKey, defaultGroupId, flags, level, blocksMinted, blocksMintedAdjustment, blocksMintedPenalty); return new AccountMessage(id, accountData); } diff --git a/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java b/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java new file mode 100644 index 00000000..62428cc0 --- /dev/null +++ b/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java @@ -0,0 +1,109 @@ +package org.qortal.network.message; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import org.qortal.data.block.BlockSummaryData; +import org.qortal.transform.Transformer; +import org.qortal.transform.block.BlockTransformer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class BlockSummariesV2Message extends Message { + + public static final long MINIMUM_PEER_VERSION = 0x0300060001L; + + private static final int BLOCK_SUMMARY_V2_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH /* block signature */ + + Transformer.PUBLIC_KEY_LENGTH /* minter public key */ + + Transformer.INT_LENGTH /* online accounts count */ + + Transformer.LONG_LENGTH /* block timestamp */ + + Transformer.INT_LENGTH /* transactions count */ + + BlockTransformer.BLOCK_SIGNATURE_LENGTH; /* block reference */ + + private List blockSummaries; + + public BlockSummariesV2Message(List blockSummaries) { + super(MessageType.BLOCK_SUMMARIES_V2); + + // Shortcut for when there are no summaries + if (blockSummaries.isEmpty()) { + this.dataBytes = Message.EMPTY_DATA_BYTES; + return; + } + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + try { + // First summary's height + bytes.write(Ints.toByteArray(blockSummaries.get(0).getHeight())); + + for (BlockSummaryData blockSummary : blockSummaries) { + bytes.write(blockSummary.getSignature()); + bytes.write(blockSummary.getMinterPublicKey()); + bytes.write(Ints.toByteArray(blockSummary.getOnlineAccountsCount())); + bytes.write(Longs.toByteArray(blockSummary.getTimestamp())); + bytes.write(Ints.toByteArray(blockSummary.getTransactionCount())); + bytes.write(blockSummary.getReference()); + } + } catch (IOException e) { + throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); + } + + this.dataBytes = bytes.toByteArray(); + this.checksumBytes = Message.generateChecksum(this.dataBytes); + } + + private BlockSummariesV2Message(int id, List blockSummaries) { + super(id, MessageType.BLOCK_SUMMARIES_V2); + + this.blockSummaries = blockSummaries; + } + + public List getBlockSummaries() { + return this.blockSummaries; + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) { + List blockSummaries = new ArrayList<>(); + + // If there are no bytes remaining then we can treat this as an empty array of summaries + if (bytes.remaining() == 0) + return new BlockSummariesV2Message(id, blockSummaries); + + int height = bytes.getInt(); + + // Expecting bytes remaining to be exact multiples of BLOCK_SUMMARY_V2_LENGTH + if (bytes.remaining() % BLOCK_SUMMARY_V2_LENGTH != 0) + throw new BufferUnderflowException(); + + while (bytes.hasRemaining()) { + byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH]; + bytes.get(signature); + + byte[] minterPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + bytes.get(minterPublicKey); + + int onlineAccountsCount = bytes.getInt(); + + long timestamp = bytes.getLong(); + + int transactionsCount = bytes.getInt(); + + byte[] reference = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH]; + bytes.get(reference); + + BlockSummaryData blockSummary = new BlockSummaryData(height, signature, minterPublicKey, + onlineAccountsCount, timestamp, transactionsCount, reference); + blockSummaries.add(blockSummary); + + height++; + } + + return new BlockSummariesV2Message(id, blockSummaries); + } + +} diff --git a/src/main/java/org/qortal/network/message/GenericUnknownMessage.java b/src/main/java/org/qortal/network/message/GenericUnknownMessage.java new file mode 100644 index 00000000..dea9f2b8 --- /dev/null +++ b/src/main/java/org/qortal/network/message/GenericUnknownMessage.java @@ -0,0 +1,23 @@ +package org.qortal.network.message; + +import java.nio.ByteBuffer; + +public class GenericUnknownMessage extends Message { + + public static final long MINIMUM_PEER_VERSION = 0x0300060001L; + + public GenericUnknownMessage() { + super(MessageType.GENERIC_UNKNOWN); + + this.dataBytes = EMPTY_DATA_BYTES; + } + + private GenericUnknownMessage(int id) { + super(id, MessageType.GENERIC_UNKNOWN); + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) { + return new GenericUnknownMessage(id); + } + +} diff --git a/src/main/java/org/qortal/network/message/GetOnlineAccountsV3Message.java b/src/main/java/org/qortal/network/message/GetOnlineAccountsV3Message.java new file mode 100644 index 00000000..66c7c47a --- /dev/null +++ b/src/main/java/org/qortal/network/message/GetOnlineAccountsV3Message.java @@ -0,0 +1,112 @@ +package org.qortal.network.message; + +import com.google.common.primitives.Longs; +import org.qortal.transform.Transformer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.*; + +/** + * For requesting online accounts info from remote peer, given our list of online accounts. + *

        + * Different format to V1 and V2:
        + *
          + *
        • V1 is: number of entries, then timestamp + pubkey for each entry
        • + *
        • V2 is: groups of: number of entries, timestamp, then pubkey for each entry
        • + *
        • V3 is: groups of: timestamp, number of entries (one per leading byte), then hash(pubkeys) for each entry
        • + *
        + *

        + * End + */ +public class GetOnlineAccountsV3Message extends Message { + + private static final Map> EMPTY_ONLINE_ACCOUNTS = Collections.emptyMap(); + private Map> hashesByTimestampThenByte; + + public GetOnlineAccountsV3Message(Map> hashesByTimestampThenByte) { + super(MessageType.GET_ONLINE_ACCOUNTS_V3); + + // If we don't have ANY online accounts then it's an easier construction... + if (hashesByTimestampThenByte.isEmpty()) { + this.dataBytes = EMPTY_DATA_BYTES; + return; + } + + // We should know exactly how many bytes to allocate now + int byteSize = hashesByTimestampThenByte.size() * (Transformer.TIMESTAMP_LENGTH + Transformer.BYTE_LENGTH); + + byteSize += hashesByTimestampThenByte.values() + .stream() + .mapToInt(map -> map.size() * Transformer.PUBLIC_KEY_LENGTH) + .sum(); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize); + + // Warning: no double-checking/fetching! We must be ConcurrentMap compatible. + // So no contains() then get() or multiple get()s on the same key/map. + try { + for (var outerMapEntry : hashesByTimestampThenByte.entrySet()) { + bytes.write(Longs.toByteArray(outerMapEntry.getKey())); + + var innerMap = outerMapEntry.getValue(); + + // Number of entries: 1 - 256, where 256 is represented by 0 + bytes.write(innerMap.size() & 0xFF); + + for (byte[] hashBytes : innerMap.values()) { + bytes.write(hashBytes); + } + } + } catch (IOException e) { + throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); + } + + this.dataBytes = bytes.toByteArray(); + this.checksumBytes = Message.generateChecksum(this.dataBytes); + } + + private GetOnlineAccountsV3Message(int id, Map> hashesByTimestampThenByte) { + super(id, MessageType.GET_ONLINE_ACCOUNTS_V3); + + this.hashesByTimestampThenByte = hashesByTimestampThenByte; + } + + public Map> getHashesByTimestampThenByte() { + return this.hashesByTimestampThenByte; + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) { + // 'empty' case + if (!bytes.hasRemaining()) { + return new GetOnlineAccountsV3Message(id, EMPTY_ONLINE_ACCOUNTS); + } + + Map> hashesByTimestampThenByte = new HashMap<>(); + + while (bytes.hasRemaining()) { + long timestamp = bytes.getLong(); + + int hashCount = bytes.get(); + if (hashCount <= 0) + // 256 is represented by 0. + // Also converts negative signed value (e.g. -1) to proper positive unsigned value (255) + hashCount += 256; + + Map hashesByByte = new HashMap<>(); + + for (int i = 0; i < hashCount; ++i) { + byte[] publicKeyHash = new byte[Transformer.PUBLIC_KEY_LENGTH]; + bytes.get(publicKeyHash); + + hashesByByte.put(publicKeyHash[0], publicKeyHash); + } + + hashesByTimestampThenByte.put(timestamp, hashesByByte); + } + + return new GetOnlineAccountsV3Message(id, hashesByTimestampThenByte); + } + +} diff --git a/src/main/java/org/qortal/network/message/Message.java b/src/main/java/org/qortal/network/message/Message.java index f752b5b9..d8467d90 100644 --- a/src/main/java/org/qortal/network/message/Message.java +++ b/src/main/java/org/qortal/network/message/Message.java @@ -46,6 +46,7 @@ public abstract class Message { private static final int MAX_DATA_SIZE = 10 * 1024 * 1024; // 10MB protected static final byte[] EMPTY_DATA_BYTES = new byte[0]; + private static final ByteBuffer EMPTY_READ_ONLY_BYTE_BUFFER = ByteBuffer.wrap(EMPTY_DATA_BYTES).asReadOnlyBuffer(); protected int id; protected final MessageType type; @@ -126,7 +127,7 @@ public abstract class Message { if (dataSize > 0 && dataSize + CHECKSUM_LENGTH > readOnlyBuffer.remaining()) return null; - ByteBuffer dataSlice = null; + ByteBuffer dataSlice = EMPTY_READ_ONLY_BYTE_BUFFER; if (dataSize > 0) { byte[] expectedChecksum = new byte[CHECKSUM_LENGTH]; readOnlyBuffer.get(expectedChecksum); diff --git a/src/main/java/org/qortal/network/message/MessageType.java b/src/main/java/org/qortal/network/message/MessageType.java index c2ae7676..4dd4a3c8 100644 --- a/src/main/java/org/qortal/network/message/MessageType.java +++ b/src/main/java/org/qortal/network/message/MessageType.java @@ -21,6 +21,7 @@ public enum MessageType { HEIGHT_V2(10, HeightV2Message::fromByteBuffer), PING(11, PingMessage::fromByteBuffer), PONG(12, PongMessage::fromByteBuffer), + GENERIC_UNKNOWN(13, GenericUnknownMessage::fromByteBuffer), // Requesting data PEERS_V2(20, PeersV2Message::fromByteBuffer), @@ -41,11 +42,14 @@ public enum MessageType { BLOCK_SUMMARIES(70, BlockSummariesMessage::fromByteBuffer), GET_BLOCK_SUMMARIES(71, GetBlockSummariesMessage::fromByteBuffer), + BLOCK_SUMMARIES_V2(72, BlockSummariesV2Message::fromByteBuffer), ONLINE_ACCOUNTS(80, OnlineAccountsMessage::fromByteBuffer), GET_ONLINE_ACCOUNTS(81, GetOnlineAccountsMessage::fromByteBuffer), ONLINE_ACCOUNTS_V2(82, OnlineAccountsV2Message::fromByteBuffer), GET_ONLINE_ACCOUNTS_V2(83, GetOnlineAccountsV2Message::fromByteBuffer), + ONLINE_ACCOUNTS_V3(84, OnlineAccountsV3Message::fromByteBuffer), + GET_ONLINE_ACCOUNTS_V3(85, GetOnlineAccountsV3Message::fromByteBuffer), ARBITRARY_DATA(90, ArbitraryDataMessage::fromByteBuffer), GET_ARBITRARY_DATA(91, GetArbitraryDataMessage::fromByteBuffer), diff --git a/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java b/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java new file mode 100644 index 00000000..d554d96c --- /dev/null +++ b/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java @@ -0,0 +1,121 @@ +package org.qortal.network.message; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import org.qortal.data.network.OnlineAccountData; +import org.qortal.transform.Transformer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * For sending online accounts info to remote peer. + * + * Same format as V2, but with added support for a mempow nonce. + */ +public class OnlineAccountsV3Message extends Message { + + public static final long MIN_PEER_VERSION = 0x300060000L; // 3.6.0 + + private List onlineAccounts; + + public OnlineAccountsV3Message(List onlineAccounts) { + super(MessageType.ONLINE_ACCOUNTS_V3); + + // Shortcut in case we have no online accounts + if (onlineAccounts.isEmpty()) { + this.dataBytes = Ints.toByteArray(0); + this.checksumBytes = Message.generateChecksum(this.dataBytes); + return; + } + + // How many of each timestamp + Map countByTimestamp = new HashMap<>(); + + for (OnlineAccountData onlineAccountData : onlineAccounts) { + Long timestamp = onlineAccountData.getTimestamp(); + countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v); + } + + // We should know exactly how many bytes to allocate now + int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH) + + onlineAccounts.size() * (Transformer.SIGNATURE_LENGTH + Transformer.PUBLIC_KEY_LENGTH); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize); + + try { + for (long timestamp : countByTimestamp.keySet()) { + bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp))); + + bytes.write(Longs.toByteArray(timestamp)); + + for (OnlineAccountData onlineAccountData : onlineAccounts) { + if (onlineAccountData.getTimestamp() == timestamp) { + bytes.write(onlineAccountData.getSignature()); + bytes.write(onlineAccountData.getPublicKey()); + + // Nonce is optional; use -1 as placeholder if missing + int nonce = onlineAccountData.getNonce() != null ? onlineAccountData.getNonce() : -1; + bytes.write(Ints.toByteArray(nonce)); + } + } + } + } catch (IOException e) { + throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); + } + + this.dataBytes = bytes.toByteArray(); + this.checksumBytes = Message.generateChecksum(this.dataBytes); + } + + private OnlineAccountsV3Message(int id, List onlineAccounts) { + super(id, MessageType.ONLINE_ACCOUNTS_V3); + + this.onlineAccounts = onlineAccounts; + } + + public List getOnlineAccounts() { + return this.onlineAccounts; + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException { + int accountCount = bytes.getInt(); + + List onlineAccounts = new ArrayList<>(accountCount); + + while (accountCount > 0) { + long timestamp = bytes.getLong(); + + for (int i = 0; i < accountCount; ++i) { + byte[] signature = new byte[Transformer.SIGNATURE_LENGTH]; + bytes.get(signature); + + byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + bytes.get(publicKey); + + // Nonce is optional - will be -1 if missing + Integer nonce = bytes.getInt(); + if (nonce < 0) { + nonce = null; + } + + onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey, nonce)); + } + + if (bytes.hasRemaining()) { + accountCount = bytes.getInt(); + } else { + // we've finished + accountCount = 0; + } + } + + return new OnlineAccountsV3Message(id, onlineAccounts); + } + +} diff --git a/src/main/java/org/qortal/repository/AccountRepository.java b/src/main/java/org/qortal/repository/AccountRepository.java index c1d31e31..1175337c 100644 --- a/src/main/java/org/qortal/repository/AccountRepository.java +++ b/src/main/java/org/qortal/repository/AccountRepository.java @@ -1,13 +1,9 @@ package org.qortal.repository; import java.util.List; +import java.util.Set; -import org.qortal.data.account.AccountBalanceData; -import org.qortal.data.account.AccountData; -import org.qortal.data.account.EligibleQoraHolderData; -import org.qortal.data.account.MintingAccountData; -import org.qortal.data.account.QortFromQoraData; -import org.qortal.data.account.RewardShareData; +import org.qortal.data.account.*; public interface AccountRepository { @@ -19,6 +15,9 @@ public interface AccountRepository { /** Returns accounts with any bit set in given mask. */ public List getFlaggedAccounts(int mask) throws DataException; + /** Returns accounts with a blockedMintedPenalty */ + public List getPenaltyAccounts() throws DataException; + /** Returns account's last reference or null if not set or account not found. */ public byte[] getLastReference(String address) throws DataException; @@ -100,6 +99,18 @@ public interface AccountRepository { */ public void modifyMintedBlockCounts(List addresses, int delta) throws DataException; + /** Returns account's block minted penalty count or null if account not found. */ + public Integer getBlocksMintedPenaltyCount(String address) throws DataException; + + /** + * Sets blocks minted penalties for given list of accounts. + * This replaces the existing values rather than modifying them by a delta. + * + * @param accountPenalties + * @throws DataException + */ + public void updateBlocksMintedPenalties(Set accountPenalties) throws DataException; + /** Delete account from repository. */ public void delete(String address) throws DataException; @@ -159,6 +170,9 @@ public interface AccountRepository { /** Returns number of active reward-shares involving passed public key as the minting account only. */ public int countRewardShares(byte[] mintingAccountPublicKey) throws DataException; + /** Returns number of active self-shares involving passed public key as the minting account only. */ + public int countSelfShares(byte[] mintingAccountPublicKey) throws DataException; + public List getRewardShares() throws DataException; public List findRewardShares(List mintingAccounts, List recipientAccounts, List involvedAddresses, Integer limit, Integer offset, Boolean reverse) throws DataException; diff --git a/src/main/java/org/qortal/repository/ArbitraryRepository.java b/src/main/java/org/qortal/repository/ArbitraryRepository.java index 7a31f40e..75fb0509 100644 --- a/src/main/java/org/qortal/repository/ArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/ArbitraryRepository.java @@ -24,7 +24,7 @@ public interface ArbitraryRepository { public ArbitraryTransactionData getLatestTransaction(String name, Service service, Method method, String identifier) throws DataException; - public List getArbitraryResources(Service service, String identifier, String name, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException; + public List getArbitraryResources(Service service, String identifier, List names, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException; public List searchArbitraryResources(Service service, String query, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException; diff --git a/src/main/java/org/qortal/repository/ChatRepository.java b/src/main/java/org/qortal/repository/ChatRepository.java index cd4b9a8f..c4541907 100644 --- a/src/main/java/org/qortal/repository/ChatRepository.java +++ b/src/main/java/org/qortal/repository/ChatRepository.java @@ -14,8 +14,8 @@ public interface ChatRepository { * Expects EITHER non-null txGroupID OR non-null sender and recipient addresses. */ public List getMessagesMatchingCriteria(Long before, Long after, - Integer txGroupId, List involving, - Integer limit, Integer offset, Boolean reverse) throws DataException; + Integer txGroupId, byte[] reference, byte[] chatReferenceBytes, Boolean hasChatReference, + List involving, Integer limit, Integer offset, Boolean reverse) throws DataException; public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException; diff --git a/src/main/java/org/qortal/repository/GroupRepository.java b/src/main/java/org/qortal/repository/GroupRepository.java index bcee7d25..94c97992 100644 --- a/src/main/java/org/qortal/repository/GroupRepository.java +++ b/src/main/java/org/qortal/repository/GroupRepository.java @@ -131,7 +131,14 @@ public interface GroupRepository { public GroupBanData getBan(int groupId, String member) throws DataException; - public boolean banExists(int groupId, String offender) throws DataException; + /** + * IMPORTANT: when using banExists() as part of validation, the timestamp must be that of the transaction that + * is calling banExists() as part of its validation. It must NOT be the current time, unless this is being + * called outside of validation, as part of an on demand check for a ban existing (such as via an API call). + * This is because we need to evaluate a ban's status based on the time of the subsequent transaction, as + * validation will not occur at a fixed time for every node. For some, it could be months into the future. + */ + public boolean banExists(int groupId, String offender, long timestamp) throws DataException; public List getGroupBans(int groupId, Integer limit, Integer offset, Boolean reverse) throws DataException; diff --git a/src/main/java/org/qortal/repository/TransactionRepository.java b/src/main/java/org/qortal/repository/TransactionRepository.java index 4fb9bb12..105a317d 100644 --- a/src/main/java/org/qortal/repository/TransactionRepository.java +++ b/src/main/java/org/qortal/repository/TransactionRepository.java @@ -179,6 +179,15 @@ public interface TransactionRepository { public List getAssetTransfers(long assetId, String address, Integer limit, Integer offset, Boolean reverse) throws DataException; + /** + * Returns list of reward share transaction creators, excluding self shares. + * This uses confirmed transactions only. + * + * @return + * @throws DataException + */ + public List getConfirmedRewardShareCreatorsExcludingSelfShares() throws DataException; + /** * Returns list of transactions pending approval, with optional txGgroupId filtering. *

        diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBAccountRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBAccountRepository.java index 028f3d46..cb188502 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBAccountRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBAccountRepository.java @@ -6,15 +6,11 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import org.qortal.asset.Asset; -import org.qortal.data.account.AccountBalanceData; -import org.qortal.data.account.AccountData; -import org.qortal.data.account.EligibleQoraHolderData; -import org.qortal.data.account.MintingAccountData; -import org.qortal.data.account.QortFromQoraData; -import org.qortal.data.account.RewardShareData; +import org.qortal.data.account.*; import org.qortal.repository.AccountRepository; import org.qortal.repository.DataException; @@ -30,7 +26,7 @@ public class HSQLDBAccountRepository implements AccountRepository { @Override public AccountData getAccount(String address) throws DataException { - String sql = "SELECT reference, public_key, default_group_id, flags, level, blocks_minted, blocks_minted_adjustment FROM Accounts WHERE account = ?"; + String sql = "SELECT reference, public_key, default_group_id, flags, level, blocks_minted, blocks_minted_adjustment, blocks_minted_penalty FROM Accounts WHERE account = ?"; try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) { if (resultSet == null) @@ -43,8 +39,9 @@ public class HSQLDBAccountRepository implements AccountRepository { int level = resultSet.getInt(5); int blocksMinted = resultSet.getInt(6); int blocksMintedAdjustment = resultSet.getInt(7); + int blocksMintedPenalty = resultSet.getInt(8); - return new AccountData(address, reference, publicKey, defaultGroupId, flags, level, blocksMinted, blocksMintedAdjustment); + return new AccountData(address, reference, publicKey, defaultGroupId, flags, level, blocksMinted, blocksMintedAdjustment, blocksMintedPenalty); } catch (SQLException e) { throw new DataException("Unable to fetch account info from repository", e); } @@ -52,7 +49,7 @@ public class HSQLDBAccountRepository implements AccountRepository { @Override public List getFlaggedAccounts(int mask) throws DataException { - String sql = "SELECT reference, public_key, default_group_id, flags, level, blocks_minted, blocks_minted_adjustment, account FROM Accounts WHERE BITAND(flags, ?) != 0"; + String sql = "SELECT reference, public_key, default_group_id, flags, level, blocks_minted, blocks_minted_adjustment, blocks_minted_penalty, account FROM Accounts WHERE BITAND(flags, ?) != 0"; List accounts = new ArrayList<>(); @@ -68,9 +65,10 @@ public class HSQLDBAccountRepository implements AccountRepository { int level = resultSet.getInt(5); int blocksMinted = resultSet.getInt(6); int blocksMintedAdjustment = resultSet.getInt(7); - String address = resultSet.getString(8); + int blocksMintedPenalty = resultSet.getInt(8); + String address = resultSet.getString(9); - accounts.add(new AccountData(address, reference, publicKey, defaultGroupId, flags, level, blocksMinted, blocksMintedAdjustment)); + accounts.add(new AccountData(address, reference, publicKey, defaultGroupId, flags, level, blocksMinted, blocksMintedAdjustment, blocksMintedPenalty)); } while (resultSet.next()); return accounts; @@ -79,6 +77,36 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override + public List getPenaltyAccounts() throws DataException { + String sql = "SELECT reference, public_key, default_group_id, flags, level, blocks_minted, blocks_minted_adjustment, blocks_minted_penalty, account FROM Accounts WHERE blocks_minted_penalty != 0"; + + List accounts = new ArrayList<>(); + + try (ResultSet resultSet = this.repository.checkedExecute(sql)) { + if (resultSet == null) + return accounts; + + do { + byte[] reference = resultSet.getBytes(1); + byte[] publicKey = resultSet.getBytes(2); + int defaultGroupId = resultSet.getInt(3); + int flags = resultSet.getInt(4); + int level = resultSet.getInt(5); + int blocksMinted = resultSet.getInt(6); + int blocksMintedAdjustment = resultSet.getInt(7); + int blocksMintedPenalty = resultSet.getInt(8); + String address = resultSet.getString(9); + + accounts.add(new AccountData(address, reference, publicKey, defaultGroupId, flags, level, blocksMinted, blocksMintedAdjustment, blocksMintedPenalty)); + } while (resultSet.next()); + + return accounts; + } catch (SQLException e) { + throw new DataException("Unable to fetch penalty accounts from repository", e); + } + } + @Override public byte[] getLastReference(String address) throws DataException { String sql = "SELECT reference FROM Accounts WHERE account = ?"; @@ -298,6 +326,39 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override + public Integer getBlocksMintedPenaltyCount(String address) throws DataException { + String sql = "SELECT blocks_minted_penalty FROM Accounts WHERE account = ?"; + + try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) { + if (resultSet == null) + return null; + + return resultSet.getInt(1); + } catch (SQLException e) { + throw new DataException("Unable to fetch account's block minted penalty count from repository", e); + } + } + public void updateBlocksMintedPenalties(Set accountPenalties) throws DataException { + // Nothing to do? + if (accountPenalties == null || accountPenalties.isEmpty()) + return; + + // Map balance changes into SQL bind params, filtering out no-op changes + List updateBlocksMintedPenaltyParams = accountPenalties.stream() + .map(accountPenalty -> new Object[] { accountPenalty.getAddress(), accountPenalty.getBlocksMintedPenalty(), accountPenalty.getBlocksMintedPenalty() }) + .collect(Collectors.toList()); + + // Perform actual balance changes + String sql = "INSERT INTO Accounts (account, blocks_minted_penalty) VALUES (?, ?) " + + "ON DUPLICATE KEY UPDATE blocks_minted_penalty = blocks_minted_penalty + ?"; + try { + this.repository.executeCheckedBatchUpdate(sql, updateBlocksMintedPenaltyParams); + } catch (SQLException e) { + throw new DataException("Unable to set blocks minted penalties in repository", e); + } + } + @Override public void delete(String address) throws DataException { // NOTE: Account balances are deleted automatically by the database thanks to "ON DELETE CASCADE" in AccountBalances' FOREIGN KEY @@ -688,6 +749,17 @@ public class HSQLDBAccountRepository implements AccountRepository { } } + @Override + public int countSelfShares(byte[] minterPublicKey) throws DataException { + String sql = "SELECT COUNT(*) FROM RewardShares WHERE minter_public_key = ? AND minter = recipient"; + + try (ResultSet resultSet = this.repository.checkedExecute(sql, minterPublicKey)) { + return resultSet.getInt(1); + } catch (SQLException e) { + throw new DataException("Unable to count self-shares in repository", e); + } + } + @Override public List getRewardShares() throws DataException { String sql = "SELECT minter_public_key, minter, recipient, share_percent, reward_share_public_key FROM RewardShares"; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java index 2c88b089..c21dd038 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java @@ -301,7 +301,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { } @Override - public List getArbitraryResources(Service service, String identifier, String name, + public List getArbitraryResources(Service service, String identifier, List names, boolean defaultResource, Integer limit, Integer offset, Boolean reverse) throws DataException { StringBuilder sql = new StringBuilder(512); List bindParams = new ArrayList<>(); @@ -325,9 +325,16 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { bindParams.add(identifier); } - if (name != null) { - sql.append(" AND name = ?"); - bindParams.add(name); + if (names != null && !names.isEmpty()) { + sql.append(" AND name IN (?"); + bindParams.add(names.get(0)); + + for (int i = 1; i < names.size(); ++i) { + sql.append(", ?"); + bindParams.add(names.get(i)); + } + + sql.append(")"); } sql.append(" GROUP BY name, service, identifier ORDER BY name COLLATE SQL_TEXT_UCC_NO_PAD"); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java index cc7e1611..c3c5638a 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java @@ -143,13 +143,17 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository { byte[] blockMinterPublicKey = resultSet.getBytes(3); // Fetch additional info from the archive itself - int onlineAccountsCount = 0; + Integer onlineAccountsCount = null; + Long timestamp = null; + Integer transactionCount = null; + byte[] reference = null; + BlockData blockData = this.fromSignature(signature); if (blockData != null) { onlineAccountsCount = blockData.getOnlineAccountsCount(); } - BlockSummaryData blockSummary = new BlockSummaryData(height, signature, blockMinterPublicKey, onlineAccountsCount); + BlockSummaryData blockSummary = new BlockSummaryData(height, signature, blockMinterPublicKey, onlineAccountsCount, timestamp, transactionCount, reference); blockSummaries.add(blockSummary); } while (resultSet.next()); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockRepository.java index b8238085..f38d549c 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockRepository.java @@ -297,7 +297,7 @@ public class HSQLDBBlockRepository implements BlockRepository { @Override public List getBlockSummariesBySigner(byte[] signerPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException { StringBuilder sql = new StringBuilder(512); - sql.append("SELECT signature, height, Blocks.minter, online_accounts_count FROM "); + sql.append("SELECT signature, height, Blocks.minter, online_accounts_count, minted_when, transaction_count, Blocks.reference FROM "); // List of minter account's public key and reward-share public keys with minter's public key sql.append("(SELECT * FROM (VALUES (CAST(? AS QortalPublicKey))) UNION (SELECT reward_share_public_key FROM RewardShares WHERE minter_public_key = ?)) AS PublicKeys (public_key) "); @@ -322,8 +322,12 @@ public class HSQLDBBlockRepository implements BlockRepository { int height = resultSet.getInt(2); byte[] blockMinterPublicKey = resultSet.getBytes(3); int onlineAccountsCount = resultSet.getInt(4); + long timestamp = resultSet.getLong(5); + int transactionCount = resultSet.getInt(6); + byte[] reference = resultSet.getBytes(7); - BlockSummaryData blockSummary = new BlockSummaryData(height, signature, blockMinterPublicKey, onlineAccountsCount); + BlockSummaryData blockSummary = new BlockSummaryData(height, signature, blockMinterPublicKey, onlineAccountsCount, + timestamp, transactionCount, reference); blockSummaries.add(blockSummary); } while (resultSet.next()); @@ -355,7 +359,7 @@ public class HSQLDBBlockRepository implements BlockRepository { @Override public List getBlockSummaries(int firstBlockHeight, int lastBlockHeight) throws DataException { - String sql = "SELECT signature, height, minter, online_accounts_count, minted_when, transaction_count " + String sql = "SELECT signature, height, minter, online_accounts_count, minted_when, transaction_count, reference " + "FROM Blocks WHERE height BETWEEN ? AND ?"; List blockSummaries = new ArrayList<>(); @@ -371,9 +375,10 @@ public class HSQLDBBlockRepository implements BlockRepository { int onlineAccountsCount = resultSet.getInt(4); long timestamp = resultSet.getLong(5); int transactionCount = resultSet.getInt(6); + byte[] reference = resultSet.getBytes(7); BlockSummaryData blockSummary = new BlockSummaryData(height, signature, minterPublicKey, onlineAccountsCount, - timestamp, transactionCount); + timestamp, transactionCount, reference); blockSummaries.add(blockSummary); } while (resultSet.next()); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 2972e9f2..08226d53 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -23,9 +23,9 @@ public class HSQLDBChatRepository implements ChatRepository { } @Override - public List getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, - List involving, Integer limit, Integer offset, Boolean reverse) - throws DataException { + public List getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes, + byte[] chatReferenceBytes, Boolean hasChatReference, List involving, + Integer limit, Integer offset, Boolean reverse) throws DataException { // Check args meet expectations if ((txGroupId != null && involving != null && !involving.isEmpty()) || (txGroupId == null && (involving == null || involving.size() != 2))) @@ -35,7 +35,7 @@ public class HSQLDBChatRepository implements ChatRepository { sql.append("SELECT created_when, tx_group_id, Transactions.reference, creator, " + "sender, SenderNames.name, recipient, RecipientNames.name, " - + "data, is_text, is_encrypted, signature " + + "chat_reference, data, is_text, is_encrypted, signature " + "FROM ChatTransactions " + "JOIN Transactions USING (signature) " + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " @@ -57,6 +57,23 @@ public class HSQLDBChatRepository implements ChatRepository { bindParams.add(after); } + if (referenceBytes != null) { + whereClauses.add("reference = ?"); + bindParams.add(referenceBytes); + } + + if (chatReferenceBytes != null) { + whereClauses.add("chat_reference = ?"); + bindParams.add(chatReferenceBytes); + } + + if (hasChatReference != null && hasChatReference == true) { + whereClauses.add("chat_reference IS NOT NULL"); + } + else if (hasChatReference != null && hasChatReference == false) { + whereClauses.add("chat_reference IS NULL"); + } + if (txGroupId != null) { whereClauses.add("tx_group_id = " + txGroupId); // int safe to use literally whereClauses.add("recipient IS NULL"); @@ -98,13 +115,14 @@ public class HSQLDBChatRepository implements ChatRepository { String senderName = resultSet.getString(6); String recipient = resultSet.getString(7); String recipientName = resultSet.getString(8); - byte[] data = resultSet.getBytes(9); - boolean isText = resultSet.getBoolean(10); - boolean isEncrypted = resultSet.getBoolean(11); - byte[] signature = resultSet.getBytes(12); + byte[] chatReference = resultSet.getBytes(9); + byte[] data = resultSet.getBytes(10); + boolean isText = resultSet.getBoolean(11); + boolean isEncrypted = resultSet.getBoolean(12); + byte[] signature = resultSet.getBytes(13); ChatMessage chatMessage = new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender, - senderName, recipient, recipientName, data, isText, isEncrypted, signature); + senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature); chatMessages.add(chatMessage); } while (resultSet.next()); @@ -136,13 +154,14 @@ public class HSQLDBChatRepository implements ChatRepository { byte[] senderPublicKey = chatTransactionData.getSenderPublicKey(); String sender = chatTransactionData.getSender(); String recipient = chatTransactionData.getRecipient(); + byte[] chatReference = chatTransactionData.getChatReference(); byte[] data = chatTransactionData.getData(); boolean isText = chatTransactionData.getIsText(); boolean isEncrypted = chatTransactionData.getIsEncrypted(); byte[] signature = chatTransactionData.getSignature(); return new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender, - senderName, recipient, recipientName, data, isText, isEncrypted, signature); + senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature); } catch (SQLException e) { throw new DataException("Unable to fetch convert chat transaction from repository", e); } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index 71e5897d..aecac034 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -969,6 +969,30 @@ public class HSQLDBDatabaseUpdates { stmt.execute("ALTER TABLE Blocks ALTER COLUMN online_accounts SET DATA TYPE VARBINARY(10240)"); break; + case 43: + // Pirate Chain requires storing addresses that are 78 bytes long (69 bytes when decoded), so increase + // from 32 to 128 to give some padding for potentially even larger addresses in the future + stmt.execute("ALTER TABLE TradeBotStates ALTER COLUMN receiving_account_info SET DATA TYPE VARBINARY(128)"); + break; + + case 44: + // Add blocks minted penalty + stmt.execute("ALTER TABLE Accounts ADD blocks_minted_penalty INTEGER NOT NULL DEFAULT 0"); + break; + + case 45: + // Add a chat reference, to allow one message to reference another, and for this to be easily + // searchable. Null values are allowed as most transactions won't have a reference. + stmt.execute("ALTER TABLE ChatTransactions ADD chat_reference Signature"); + // For finding chat messages by reference + stmt.execute("CREATE INDEX ChatTransactionsChatReferenceIndex ON ChatTransactions (chat_reference)"); + break; + + case 46: + // We need to track the sale price when canceling a name sale, so it can be put back when orphaned + stmt.execute("ALTER TABLE CancelSellNameTransactions ADD sale_price QortalAmount"); + break; + default: // nothing to do return false; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBGroupRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBGroupRepository.java index 91db22f1..b1cd40a0 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBGroupRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBGroupRepository.java @@ -777,9 +777,9 @@ public class HSQLDBGroupRepository implements GroupRepository { } @Override - public boolean banExists(int groupId, String offender) throws DataException { + public boolean banExists(int groupId, String offender, long timestamp) throws DataException { try { - return this.repository.exists("GroupBans", "group_id = ? AND offender = ?", groupId, offender); + return this.repository.exists("GroupBans", "group_id = ? AND offender = ? AND (expires_when IS NULL OR expires_when > ?)", groupId, offender, timestamp); } catch (SQLException e) { throw new DataException("Unable to check for group ban in repository", e); } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBMessageRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBMessageRepository.java index f00c79fc..f31c5cd8 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBMessageRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBMessageRepository.java @@ -28,7 +28,6 @@ public class HSQLDBMessageRepository implements MessageRepository { StringBuilder sql = new StringBuilder(1024); sql.append("SELECT signature from MessageTransactions " + "JOIN Transactions USING (signature) " - + "JOIN BlockTransactions ON transaction_signature = signature " + "WHERE "); List whereClauses = new ArrayList<>(); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java index 61f4b76f..6ec30e20 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java @@ -23,7 +23,6 @@ import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.qortal.account.PrivateKeyAccount; import org.qortal.crypto.Crypto; import org.qortal.globalization.Translator; import org.qortal.gui.SysTray; @@ -1003,7 +1002,7 @@ public class HSQLDBRepository implements Repository { if (privateKey == null) return null; - return PrivateKeyAccount.toPublicKey(privateKey); + return Crypto.toPublicKey(privateKey); } public static String ed25519PublicKeyToAddress(byte[] publicKey) { diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBCancelSellNameTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBCancelSellNameTransactionRepository.java index 5f2ea35a..fc8e0bb3 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBCancelSellNameTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBCancelSellNameTransactionRepository.java @@ -17,15 +17,16 @@ public class HSQLDBCancelSellNameTransactionRepository extends HSQLDBTransaction } TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException { - String sql = "SELECT name FROM CancelSellNameTransactions WHERE signature = ?"; + String sql = "SELECT name, sale_price FROM CancelSellNameTransactions WHERE signature = ?"; try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) { if (resultSet == null) return null; String name = resultSet.getString(1); + Long salePrice = resultSet.getLong(2); - return new CancelSellNameTransactionData(baseTransactionData, name); + return new CancelSellNameTransactionData(baseTransactionData, name, salePrice); } catch (SQLException e) { throw new DataException("Unable to fetch cancel sell name transaction from repository", e); } @@ -38,7 +39,7 @@ public class HSQLDBCancelSellNameTransactionRepository extends HSQLDBTransaction HSQLDBSaver saveHelper = new HSQLDBSaver("CancelSellNameTransactions"); saveHelper.bind("signature", cancelSellNameTransactionData.getSignature()).bind("owner", cancelSellNameTransactionData.getOwnerPublicKey()).bind("name", - cancelSellNameTransactionData.getName()); + cancelSellNameTransactionData.getName()).bind("sale_price", cancelSellNameTransactionData.getSalePrice()); try { saveHelper.execute(this.repository); diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java index 449922f4..79e798a9 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java @@ -17,7 +17,7 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository } TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException { - String sql = "SELECT sender, nonce, recipient, is_text, is_encrypted, data FROM ChatTransactions WHERE signature = ?"; + String sql = "SELECT sender, nonce, recipient, is_text, is_encrypted, data, chat_reference FROM ChatTransactions WHERE signature = ?"; try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) { if (resultSet == null) @@ -29,8 +29,9 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository boolean isText = resultSet.getBoolean(4); boolean isEncrypted = resultSet.getBoolean(5); byte[] data = resultSet.getBytes(6); + byte[] chatReference = resultSet.getBytes(7); - return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, data, isText, isEncrypted); + return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, chatReference, data, isText, isEncrypted); } catch (SQLException e) { throw new DataException("Unable to fetch chat transaction from repository", e); } @@ -45,7 +46,7 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository saveHelper.bind("signature", chatTransactionData.getSignature()).bind("nonce", chatTransactionData.getNonce()) .bind("sender", chatTransactionData.getSender()).bind("recipient", chatTransactionData.getRecipient()) .bind("is_text", chatTransactionData.getIsText()).bind("is_encrypted", chatTransactionData.getIsEncrypted()) - .bind("data", chatTransactionData.getData()); + .bind("data", chatTransactionData.getData()).bind("chat_reference", chatTransactionData.getChatReference()); try { saveHelper.execute(this.repository); diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index e3ef13be..a8df1ab5 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -7,11 +7,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; +import java.util.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -969,6 +965,33 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } } + public List getConfirmedRewardShareCreatorsExcludingSelfShares() throws DataException { + List rewardShareCreators = new ArrayList<>(); + + String sql = "SELECT account " + + "FROM RewardShareTransactions " + + "JOIN Accounts ON Accounts.public_key = RewardShareTransactions.minter_public_key " + + "JOIN Transactions ON Transactions.signature = RewardShareTransactions.signature " + + "WHERE block_height IS NOT NULL AND RewardShareTransactions.recipient != Accounts.account " + + "GROUP BY account " + + "ORDER BY account"; + + try (ResultSet resultSet = this.repository.checkedExecute(sql)) { + if (resultSet == null) + return rewardShareCreators; + + do { + String address = resultSet.getString(1); + + rewardShareCreators.add(address); + } while (resultSet.next()); + + return rewardShareCreators; + } catch (SQLException e) { + throw new DataException("Unable to fetch reward share creators from repository", e); + } + } + @Override public List getApprovalPendingTransactions(Integer txGroupId, Integer limit, Integer offset, Boolean reverse) throws DataException { StringBuilder sql = new StringBuilder(512); diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index a40df9b4..5799bd26 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -28,6 +28,7 @@ import org.qortal.crosschain.Litecoin.LitecoinNet; import org.qortal.crosschain.Dogecoin.DogecoinNet; import org.qortal.crosschain.Digibyte.DigibyteNet; import org.qortal.crosschain.Ravencoin.RavencoinNet; +import org.qortal.crosschain.PirateChain.PirateChainNet; import org.qortal.utils.EnumUtils; // All properties to be converted to JSON via JAXB @@ -109,7 +110,13 @@ public class Settings { /** Maximum number of unconfirmed transactions allowed per account */ private int maxUnconfirmedPerAccount = 25; /** Max milliseconds into future for accepting new, unconfirmed transactions */ - private int maxTransactionTimestampFuture = 24 * 60 * 60 * 1000; // milliseconds + private int maxTransactionTimestampFuture = 30 * 60 * 1000; // milliseconds + + /** Maximum number of CHAT transactions allowed per account in recent timeframe */ + private int maxRecentChatMessagesPerAccount = 250; + /** Maximum age of a CHAT transaction to be considered 'recent' */ + private long recentChatMessagesMaxAge = 60 * 60 * 1000L; // milliseconds + /** Whether we check, fetch and install auto-updates */ private boolean autoUpdateEnabled = true; /** How long between repository backups (ms), or 0 if disabled. */ @@ -152,7 +159,7 @@ public class Settings { * This prevents the node from being able to serve older blocks */ private boolean topOnly = false; /** The amount of recent blocks we should keep when pruning */ - private int pruneBlockLimit = 1450; + private int pruneBlockLimit = 6000; /** How often to attempt AT state pruning (ms). */ private long atStatesPruneInterval = 3219L; // milliseconds @@ -183,6 +190,8 @@ public class Settings { // Peer-to-peer related private boolean isTestNet = false; + /** Single node testnet mode */ + private boolean singleNodeTestnet = false; /** Port number for inbound peer-to-peer connections. */ private Integer listenPort; /** Whether to attempt to open the listen port via UPnP */ @@ -202,17 +211,20 @@ public class Settings { /** Maximum number of retry attempts if a peer fails to respond with the requested data */ private int maxRetries = 2; + /** The number of seconds of no activity before recovery mode begins */ + public long recoveryModeTimeout = 10 * 60 * 1000L; + /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "3.1.0"; + private String minPeerVersion = "3.8.2"; /** Whether to allow connections with peers below minPeerVersion * If true, we won't sync with them but they can still sync with us, and will show in the peers list * If false, sync will be blocked both ways, and they will not appear in the peers list */ private boolean allowConnectionsWithOlderPeerVersions = true; /** Minimum time (in seconds) that we should attempt to remain connected to a peer for */ - private int minPeerConnectionTime = 5 * 60; // seconds + private int minPeerConnectionTime = 60 * 60; // seconds /** Maximum time (in seconds) that we should attempt to remain connected to a peer for */ - private int maxPeerConnectionTime = 60 * 60; // seconds + private int maxPeerConnectionTime = 4 * 60 * 60; // seconds /** Maximum time (in seconds) that a peer should remain connected when requesting QDN data */ private int maxDataPeerConnectionTime = 2 * 60; // seconds @@ -232,10 +244,16 @@ public class Settings { private DogecoinNet dogecoinNet = DogecoinNet.MAIN; private DigibyteNet digibyteNet = DigibyteNet.MAIN; private RavencoinNet ravencoinNet = RavencoinNet.MAIN; + private PirateChainNet pirateChainNet = PirateChainNet.MAIN; // Also crosschain-related: /** Whether to show SysTray pop-up notifications when trade-bot entries change state */ private boolean tradebotSystrayEnabled = false; + /** Wallets path - used for storing encrypted wallet caches for coins that require them */ + private String walletsPath = "wallets"; + + private int arrrDefaultBirthday = 2000000; + // Repository related /** Queries that take longer than this are logged. (milliseconds) */ private Long slowQueryThreshold = null; @@ -255,7 +273,7 @@ public class Settings { private String[] bootstrapHosts = new String[] { "http://bootstrap.qortal.org", "http://bootstrap2.qortal.org", - "http://62.171.190.193" + "http://bootstrap.qortal.online" }; // Auto-update sources @@ -284,6 +302,17 @@ public class Settings { private Long testNtpOffset = null; + + /* Foreign chains */ + + /** The number of consecutive empty addresses required before treating a wallet's transaction set as complete */ + private int gapLimit = 24; + + /** How many wallet keys to generate when using bitcoinj as the blockchain interface (e.g. when sending coins) */ + private int bitcoinjLookaheadSize = 50; + + + // Data storage (QDN) /** Data storage enabled/disabled*/ @@ -468,7 +497,7 @@ public class Settings { private void validate() { // Validation goes here - if (this.minBlockchainPeers < 1) + if (this.minBlockchainPeers < 1 && !singleNodeTestnet) throwValidationError("minBlockchainPeers must be at least 1"); if (this.apiKey != null && this.apiKey.trim().length() < 8) @@ -617,6 +646,14 @@ public class Settings { return this.maxTransactionTimestampFuture; } + public int getMaxRecentChatMessagesPerAccount() { + return this.maxRecentChatMessagesPerAccount; + } + + public long getRecentChatMessagesMaxAge() { + return recentChatMessagesMaxAge; + } + public int getBlockCacheSize() { return this.blockCacheSize; } @@ -625,6 +662,10 @@ public class Settings { return this.isTestNet; } + public boolean isSingleNodeTestnet() { + return this.singleNodeTestnet; + } + public int getListenPort() { if (this.listenPort != null) return this.listenPort; @@ -645,6 +686,9 @@ public class Settings { } public int getMinBlockchainPeers() { + if (singleNodeTestnet) + return 0; + return this.minBlockchainPeers; } @@ -670,6 +714,10 @@ public class Settings { public int getMaxRetries() { return this.maxRetries; } + public long getRecoveryModeTimeout() { + return recoveryModeTimeout; + } + public String getMinPeerVersion() { return this.minPeerVersion; } public boolean getAllowConnectionsWithOlderPeerVersions() { return this.allowConnectionsWithOlderPeerVersions; } @@ -706,6 +754,18 @@ public class Settings { return this.ravencoinNet; } + public PirateChainNet getPirateChainNet() { + return this.pirateChainNet; + } + + public String getWalletsPath() { + return this.walletsPath; + } + + public int getArrrDefaultBirthday() { + return this.arrrDefaultBirthday; + } + public boolean isTradebotSystrayEnabled() { return this.tradebotSystrayEnabled; } @@ -872,6 +932,15 @@ public class Settings { } + public int getGapLimit() { + return this.gapLimit; + } + + public int getBitcoinjLookaheadSize() { + return bitcoinjLookaheadSize; + } + + public boolean isQdnEnabled() { return this.qdnEnabled; } diff --git a/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java b/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java index 15dc51bf..f38638c5 100644 --- a/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java +++ b/src/main/java/org/qortal/transaction/AddGroupAdminTransaction.java @@ -2,6 +2,7 @@ package org.qortal.transaction; import java.util.Collections; import java.util.List; +import java.util.Objects; import org.qortal.account.Account; import org.qortal.asset.Asset; @@ -64,15 +65,24 @@ public class AddGroupAdminTransaction extends Transaction { Account owner = getOwner(); String groupOwner = this.repository.getGroupRepository().getOwner(groupId); + boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS); - // Check transaction's public key matches group's current owner - if (!owner.getAddress().equals(groupOwner)) + // Require approval if transaction relates to a group owned by the null account + if (groupOwnedByNullAccount && !this.needsGroupApproval()) + return ValidationResult.GROUP_APPROVAL_REQUIRED; + + // Check transaction's public key matches group's current owner (except for groups owned by the null account) + if (!groupOwnedByNullAccount && !owner.getAddress().equals(groupOwner)) return ValidationResult.INVALID_GROUP_OWNER; // Check address is a group member if (!this.repository.getGroupRepository().memberExists(groupId, memberAddress)) return ValidationResult.NOT_GROUP_MEMBER; + // Check transaction creator is a group member + if (!this.repository.getGroupRepository().memberExists(groupId, this.getCreator().getAddress())) + return ValidationResult.NOT_GROUP_MEMBER; + // Check group member is not already an admin if (this.repository.getGroupRepository().adminExists(groupId, memberAddress)) return ValidationResult.ALREADY_GROUP_ADMIN; diff --git a/src/main/java/org/qortal/transaction/ArbitraryTransaction.java b/src/main/java/org/qortal/transaction/ArbitraryTransaction.java index ca5ce517..50d8ccad 100644 --- a/src/main/java/org/qortal/transaction/ArbitraryTransaction.java +++ b/src/main/java/org/qortal/transaction/ArbitraryTransaction.java @@ -24,6 +24,7 @@ import org.qortal.transform.Transformer; import org.qortal.transform.transaction.ArbitraryTransactionTransformer; import org.qortal.transform.transaction.TransactionTransformer; import org.qortal.utils.ArbitraryTransactionUtils; +import org.qortal.utils.NTP; public class ArbitraryTransaction extends Transaction { @@ -34,9 +35,13 @@ public class ArbitraryTransaction extends Transaction { public static final int MAX_DATA_SIZE = 4000; public static final int MAX_METADATA_LENGTH = 32; public static final int HASH_LENGTH = TransactionTransformer.SHA256_LENGTH; - public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes public static final int MAX_IDENTIFIER_LENGTH = 64; + /** If time difference between transaction and now is greater than this then we don't verify proof-of-work. */ + public static final long HISTORIC_THRESHOLD = 2 * 7 * 24 * 60 * 60 * 1000L; + public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes + + // Constructors public ArbitraryTransaction(Repository repository, TransactionData transactionData) { @@ -202,9 +207,11 @@ public class ArbitraryTransaction extends Transaction { // Clear nonce from transactionBytes ArbitraryTransactionTransformer.clearNonce(transactionBytes); - // Check nonce - int difficulty = ArbitraryDataManager.getInstance().getPowDifficulty(); - return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, difficulty, nonce); + // We only need to check nonce for recent transactions due to PoW verification overhead + if (NTP.getTime() - this.arbitraryTransactionData.getTimestamp() < HISTORIC_THRESHOLD) { + int difficulty = ArbitraryDataManager.getInstance().getPowDifficulty(); + return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, difficulty, nonce); + } } return true; diff --git a/src/main/java/org/qortal/transaction/CancelGroupBanTransaction.java b/src/main/java/org/qortal/transaction/CancelGroupBanTransaction.java index 483dfc6f..08d9cb3e 100644 --- a/src/main/java/org/qortal/transaction/CancelGroupBanTransaction.java +++ b/src/main/java/org/qortal/transaction/CancelGroupBanTransaction.java @@ -73,7 +73,7 @@ public class CancelGroupBanTransaction extends Transaction { Account member = getMember(); // Check ban actually exists - if (!this.repository.getGroupRepository().banExists(groupId, member.getAddress())) + if (!this.repository.getGroupRepository().banExists(groupId, member.getAddress(), this.groupUnbanTransactionData.getTimestamp())) return ValidationResult.BAN_UNKNOWN; // Check admin has enough funds diff --git a/src/main/java/org/qortal/transaction/ChatTransaction.java b/src/main/java/org/qortal/transaction/ChatTransaction.java index 2671c209..5ed96494 100644 --- a/src/main/java/org/qortal/transaction/ChatTransaction.java +++ b/src/main/java/org/qortal/transaction/ChatTransaction.java @@ -1,7 +1,9 @@ package org.qortal.transaction; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Predicate; import org.qortal.account.Account; import org.qortal.account.PublicKeyAccount; @@ -16,9 +18,11 @@ import org.qortal.list.ResourceListManager; import org.qortal.repository.DataException; import org.qortal.repository.GroupRepository; import org.qortal.repository.Repository; +import org.qortal.settings.Settings; import org.qortal.transform.TransformationException; import org.qortal.transform.transaction.ChatTransactionTransformer; import org.qortal.transform.transaction.TransactionTransformer; +import org.qortal.utils.NTP; public class ChatTransaction extends Transaction { @@ -26,10 +30,11 @@ public class ChatTransaction extends Transaction { private ChatTransactionData chatTransactionData; // Other useful constants - public static final int MAX_DATA_SIZE = 256; + public static final int MAX_DATA_SIZE = 4000; public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes - public static final int POW_DIFFICULTY_WITH_QORT = 8; // leading zero bits - public static final int POW_DIFFICULTY_NO_QORT = 12; // leading zero bits + public static final int POW_DIFFICULTY_ABOVE_QORT_THRESHOLD = 8; // leading zero bits + public static final int POW_DIFFICULTY_BELOW_QORT_THRESHOLD = 18; // leading zero bits + public static final long POW_QORT_THRESHOLD = 400000000L; // Constructors @@ -78,7 +83,7 @@ public class ChatTransaction extends Transaction { // Clear nonce from transactionBytes ChatTransactionTransformer.clearNonce(transactionBytes); - int difficulty = this.getSender().getConfirmedBalance(Asset.QORT) > 0 ? POW_DIFFICULTY_WITH_QORT : POW_DIFFICULTY_NO_QORT; + int difficulty = this.getSender().getConfirmedBalance(Asset.QORT) >= POW_QORT_THRESHOLD ? POW_DIFFICULTY_ABOVE_QORT_THRESHOLD : POW_DIFFICULTY_BELOW_QORT_THRESHOLD; // Calculate nonce this.chatTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, difficulty)); @@ -145,6 +150,11 @@ public class ChatTransaction extends Transaction { public ValidationResult isValid() throws DataException { // Nonce checking is done via isSignatureValid() as that method is only called once per import + // Disregard messages with timestamp too far in the future (we have stricter limits for CHAT transactions) + if (this.chatTransactionData.getTimestamp() > NTP.getTime() + (5 * 60 * 1000L)) { + return ValidationResult.TIMESTAMP_TOO_NEW; + } + // Check for blocked author by address ResourceListManager listManager = ResourceListManager.getInstance(); if (listManager.listContains("blockedAddresses", this.chatTransactionData.getSender(), true)) { @@ -163,6 +173,14 @@ public class ChatTransaction extends Transaction { } } + PublicKeyAccount creator = this.getCreator(); + if (creator == null) + return ValidationResult.MISSING_CREATOR; + + // Reject if unconfirmed pile already has X recent CHAT transactions from same creator + if (countRecentChatTransactionsByCreator(creator) >= Settings.getInstance().getMaxRecentChatMessagesPerAccount()) + return ValidationResult.TOO_MANY_UNCONFIRMED; + // If we exist in the repository then we've been imported as unconfirmed, // but we don't want to make it into a block, so return fake non-OK result. if (this.repository.getTransactionRepository().exists(this.chatTransactionData.getSignature())) @@ -204,7 +222,7 @@ public class ChatTransaction extends Transaction { int difficulty; try { - difficulty = this.getSender().getConfirmedBalance(Asset.QORT) > 0 ? POW_DIFFICULTY_WITH_QORT : POW_DIFFICULTY_NO_QORT; + difficulty = this.getSender().getConfirmedBalance(Asset.QORT) >= POW_QORT_THRESHOLD ? POW_DIFFICULTY_ABOVE_QORT_THRESHOLD : POW_DIFFICULTY_BELOW_QORT_THRESHOLD; } catch (DataException e) { return false; } @@ -213,6 +231,26 @@ public class ChatTransaction extends Transaction { return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, difficulty, nonce); } + private int countRecentChatTransactionsByCreator(PublicKeyAccount creator) throws DataException { + List unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(); + final Long now = NTP.getTime(); + long recentThreshold = Settings.getInstance().getRecentChatMessagesMaxAge(); + + // We only care about chat transactions, and only those that are considered 'recent' + Predicate hasSameCreatorAndIsRecentChat = transactionData -> { + if (transactionData.getType() != TransactionType.CHAT) + return false; + + if (transactionData.getTimestamp() < now - recentThreshold) + return false; + + return Arrays.equals(creator.getPublicKey(), transactionData.getCreatorPublicKey()); + }; + + return (int) unconfirmedTransactions.stream().filter(hasSameCreatorAndIsRecentChat).count(); + } + + /** * Ensure there's at least a skeleton account so people * can retrieve sender's public key using address, even if all their messages diff --git a/src/main/java/org/qortal/transaction/GroupInviteTransaction.java b/src/main/java/org/qortal/transaction/GroupInviteTransaction.java index f3b08f59..fa5e7b85 100644 --- a/src/main/java/org/qortal/transaction/GroupInviteTransaction.java +++ b/src/main/java/org/qortal/transaction/GroupInviteTransaction.java @@ -78,7 +78,7 @@ public class GroupInviteTransaction extends Transaction { return ValidationResult.ALREADY_GROUP_MEMBER; // Check invitee is not banned - if (this.repository.getGroupRepository().banExists(groupId, invitee.getAddress())) + if (this.repository.getGroupRepository().banExists(groupId, invitee.getAddress(), this.groupInviteTransactionData.getTimestamp())) return ValidationResult.BANNED_FROM_GROUP; // Check creator has enough funds diff --git a/src/main/java/org/qortal/transaction/JoinGroupTransaction.java b/src/main/java/org/qortal/transaction/JoinGroupTransaction.java index bc62c629..3061a3fb 100644 --- a/src/main/java/org/qortal/transaction/JoinGroupTransaction.java +++ b/src/main/java/org/qortal/transaction/JoinGroupTransaction.java @@ -53,7 +53,7 @@ public class JoinGroupTransaction extends Transaction { return ValidationResult.ALREADY_GROUP_MEMBER; // Check member is not banned - if (this.repository.getGroupRepository().banExists(groupId, joiner.getAddress())) + if (this.repository.getGroupRepository().banExists(groupId, joiner.getAddress(), this.joinGroupTransactionData.getTimestamp())) return ValidationResult.BANNED_FROM_GROUP; // Check join request doesn't already exist diff --git a/src/main/java/org/qortal/transaction/PresenceTransaction.java b/src/main/java/org/qortal/transaction/PresenceTransaction.java index d0f54548..8076997c 100644 --- a/src/main/java/org/qortal/transaction/PresenceTransaction.java +++ b/src/main/java/org/qortal/transaction/PresenceTransaction.java @@ -12,7 +12,6 @@ import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.account.Account; -import org.qortal.controller.Controller; import org.qortal.controller.OnlineAccountsManager; import org.qortal.controller.tradebot.TradeBot; import org.qortal.crosschain.ACCT; @@ -49,7 +48,7 @@ public class PresenceTransaction extends Transaction { REWARD_SHARE(0) { @Override public long getLifetime() { - return OnlineAccountsManager.ONLINE_TIMESTAMP_MODULUS; + return OnlineAccountsManager.getOnlineTimestampModulus(); } }, TRADE_BOT(1) { diff --git a/src/main/java/org/qortal/transaction/PublicizeTransaction.java b/src/main/java/org/qortal/transaction/PublicizeTransaction.java index c03c8283..76fef00b 100644 --- a/src/main/java/org/qortal/transaction/PublicizeTransaction.java +++ b/src/main/java/org/qortal/transaction/PublicizeTransaction.java @@ -4,7 +4,9 @@ import java.util.Collections; import java.util.List; import org.qortal.account.Account; +import org.qortal.account.PublicKeyAccount; import org.qortal.api.resource.TransactionsResource.ConfirmationStatus; +import org.qortal.asset.Asset; import org.qortal.crypto.MemoryPoW; import org.qortal.data.transaction.PublicizeTransactionData; import org.qortal.data.transaction.TransactionData; @@ -26,7 +28,7 @@ public class PublicizeTransaction extends Transaction { /** If time difference between transaction and now is greater than this then we don't verify proof-of-work. */ public static final long HISTORIC_THRESHOLD = 2 * 7 * 24 * 60 * 60 * 1000L; public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes - public static final int POW_DIFFICULTY = 15; // leading zero bits + public static final int POW_DIFFICULTY = 14; // leading zero bits // Constructors @@ -102,6 +104,12 @@ public class PublicizeTransaction extends Transaction { if (!verifyNonce()) return ValidationResult.INCORRECT_NONCE; + // Validate fee if one has been included + PublicKeyAccount creator = this.getCreator(); + if (this.transactionData.getFee() > 0) + if (creator.getConfirmedBalance(Asset.QORT) < this.transactionData.getFee()) + return ValidationResult.NO_BALANCE; + return ValidationResult.OK; } diff --git a/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java b/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java index 3e5f1e6d..043b5423 100644 --- a/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java +++ b/src/main/java/org/qortal/transaction/RemoveGroupAdminTransaction.java @@ -2,6 +2,7 @@ package org.qortal.transaction; import java.util.Collections; import java.util.List; +import java.util.Objects; import org.qortal.account.Account; import org.qortal.asset.Asset; @@ -65,11 +66,21 @@ public class RemoveGroupAdminTransaction extends Transaction { return ValidationResult.GROUP_DOES_NOT_EXIST; Account owner = getOwner(); + String groupOwner = this.repository.getGroupRepository().getOwner(groupId); + boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS); - // Check transaction's public key matches group's current owner - if (!owner.getAddress().equals(groupData.getOwner())) + // Require approval if transaction relates to a group owned by the null account + if (groupOwnedByNullAccount && !this.needsGroupApproval()) + return ValidationResult.GROUP_APPROVAL_REQUIRED; + + // Check transaction's public key matches group's current owner (except for groups owned by the null account) + if (!groupOwnedByNullAccount && !owner.getAddress().equals(groupOwner)) return ValidationResult.INVALID_GROUP_OWNER; + // Check transaction creator is a group member + if (!this.repository.getGroupRepository().memberExists(groupId, this.getCreator().getAddress())) + return ValidationResult.NOT_GROUP_MEMBER; + Account admin = getAdmin(); // Check member is an admin diff --git a/src/main/java/org/qortal/transaction/RewardShareTransaction.java b/src/main/java/org/qortal/transaction/RewardShareTransaction.java index be68196d..d4d2434c 100644 --- a/src/main/java/org/qortal/transaction/RewardShareTransaction.java +++ b/src/main/java/org/qortal/transaction/RewardShareTransaction.java @@ -140,8 +140,21 @@ public class RewardShareTransaction extends Transaction { // Check the minting account hasn't reach maximum number of reward-shares int rewardShareCount = this.repository.getAccountRepository().countRewardShares(creator.getPublicKey()); - if (rewardShareCount >= BlockChain.getInstance().getMaxRewardSharesPerMintingAccount()) + int selfShareCount = this.repository.getAccountRepository().countSelfShares(creator.getPublicKey()); + + int maxRewardShares = BlockChain.getInstance().getMaxRewardSharesAtTimestamp(this.rewardShareTransactionData.getTimestamp()); + if (creator.isFounder()) + // Founders have a different limit + maxRewardShares = BlockChain.getInstance().getMaxRewardSharesPerFounderMintingAccount(); + + if (rewardShareCount >= maxRewardShares) return ValidationResult.MAXIMUM_REWARD_SHARES; + + // When filling all reward share slots, one must be a self share (after feature trigger timestamp) + if (this.rewardShareTransactionData.getTimestamp() >= BlockChain.getInstance().getRewardShareLimitTimestamp()) + if (!isRecipientAlsoMinter && rewardShareCount == maxRewardShares-1 && selfShareCount == 0) + return ValidationResult.MAXIMUM_REWARD_SHARES; + } else { // This transaction intends to modify/terminate an existing reward-share @@ -150,9 +163,12 @@ public class RewardShareTransaction extends Transaction { return ValidationResult.SELF_SHARE_EXISTS; } - // Fee checking needed if not setting up new self-share - if (!(isRecipientAlsoMinter && existingRewardShareData == null)) - // Check creator has enough funds + // Check creator has enough funds + if (this.rewardShareTransactionData.getTimestamp() >= BlockChain.getInstance().getFeeValidationFixTimestamp()) + if (creator.getConfirmedBalance(Asset.QORT) < this.rewardShareTransactionData.getFee()) + return ValidationResult.NO_BALANCE; + + else if (!(isRecipientAlsoMinter && existingRewardShareData == null)) if (creator.getConfirmedBalance(Asset.QORT) < this.rewardShareTransactionData.getFee()) return ValidationResult.NO_BALANCE; diff --git a/src/main/java/org/qortal/transaction/Transaction.java b/src/main/java/org/qortal/transaction/Transaction.java index b56d48cf..f0e9b3f6 100644 --- a/src/main/java/org/qortal/transaction/Transaction.java +++ b/src/main/java/org/qortal/transaction/Transaction.java @@ -1,13 +1,7 @@ package org.qortal.transaction; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; @@ -69,8 +63,8 @@ public abstract class Transaction { AT(21, false), CREATE_GROUP(22, true), UPDATE_GROUP(23, true), - ADD_GROUP_ADMIN(24, false), - REMOVE_GROUP_ADMIN(25, false), + ADD_GROUP_ADMIN(24, true), + REMOVE_GROUP_ADMIN(25, true), GROUP_BAN(26, false), CANCEL_GROUP_BAN(27, false), GROUP_KICK(28, false), @@ -250,6 +244,8 @@ public abstract class Transaction { INVALID_TIMESTAMP_SIGNATURE(95), ADDRESS_BLOCKED(96), NAME_BLOCKED(97), + GROUP_APPROVAL_REQUIRED(98), + ACCOUNT_NOT_TRANSFERABLE(99), INVALID_BUT_OK(999), NOT_YET_RELEASED(1000); @@ -760,9 +756,13 @@ public abstract class Transaction { // Group no longer exists? Possibly due to blockchain orphaning undoing group creation? return true; // stops tx being included in block but it will eventually expire + String groupOwner = this.repository.getGroupRepository().getOwner(txGroupId); + boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS); + // If transaction's creator is group admin (of group with ID txGroupId) then auto-approve + // This is disabled for null-owned groups, since these require approval from other admins PublicKeyAccount creator = this.getCreator(); - if (groupRepository.adminExists(txGroupId, creator.getAddress())) + if (!groupOwnedByNullAccount && groupRepository.adminExists(txGroupId, creator.getAddress())) return false; return true; diff --git a/src/main/java/org/qortal/transaction/TransferPrivsTransaction.java b/src/main/java/org/qortal/transaction/TransferPrivsTransaction.java index f6a9de68..97e67160 100644 --- a/src/main/java/org/qortal/transaction/TransferPrivsTransaction.java +++ b/src/main/java/org/qortal/transaction/TransferPrivsTransaction.java @@ -67,6 +67,11 @@ public class TransferPrivsTransaction extends Transaction { if (getSender().getConfirmedBalance(Asset.QORT) < this.transferPrivsTransactionData.getFee()) return ValidationResult.NO_BALANCE; + // Check sender doesn't have a blocksMintedPenalty, as these accounts cannot be transferred + AccountData senderAccountData = this.repository.getAccountRepository().getAccount(getSender().getAddress()); + if (senderAccountData == null || senderAccountData.getBlocksMintedPenalty() != 0) + return ValidationResult.ACCOUNT_NOT_TRANSFERABLE; + return ValidationResult.OK; } diff --git a/src/main/java/org/qortal/transaction/UpdateGroupTransaction.java b/src/main/java/org/qortal/transaction/UpdateGroupTransaction.java index 9664ccbf..27580430 100644 --- a/src/main/java/org/qortal/transaction/UpdateGroupTransaction.java +++ b/src/main/java/org/qortal/transaction/UpdateGroupTransaction.java @@ -103,7 +103,7 @@ public class UpdateGroupTransaction extends Transaction { Account newOwner = getNewOwner(); // Check new owner is not banned - if (this.repository.getGroupRepository().banExists(this.updateGroupTransactionData.getGroupId(), newOwner.getAddress())) + if (this.repository.getGroupRepository().banExists(this.updateGroupTransactionData.getGroupId(), newOwner.getAddress(), this.updateGroupTransactionData.getTimestamp())) return ValidationResult.BANNED_FROM_GROUP; return ValidationResult.OK; diff --git a/src/main/java/org/qortal/transform/block/BlockTransformer.java b/src/main/java/org/qortal/transform/block/BlockTransformer.java index b61d6900..c97aa090 100644 --- a/src/main/java/org/qortal/transform/block/BlockTransformer.java +++ b/src/main/java/org/qortal/transform/block/BlockTransformer.java @@ -235,7 +235,7 @@ public class BlockTransformer extends Transformer { // Online accounts timestamp is only present if there are also signatures onlineAccountsTimestamp = byteBuffer.getLong(); - final int signaturesByteLength = onlineAccountsSignaturesCount * Transformer.SIGNATURE_LENGTH; + final int signaturesByteLength = (onlineAccountsSignaturesCount * Transformer.SIGNATURE_LENGTH) + (onlineAccountsCount * INT_LENGTH); if (signaturesByteLength > BlockChain.getInstance().getMaxBlockSize()) throw new TransformationException("Byte data too long for online accounts signatures"); @@ -371,7 +371,7 @@ public class BlockTransformer extends Transformer { if (onlineAccountsSignatures != null && onlineAccountsSignatures.length > 0) { // Note: we write the number of signatures, not the number of bytes - bytes.write(Ints.toByteArray(onlineAccountsSignatures.length / Transformer.SIGNATURE_LENGTH)); + bytes.write(Ints.toByteArray(blockData.getOnlineAccountsSignaturesCount())); // We only write online accounts timestamp if we have signatures bytes.write(Longs.toByteArray(blockData.getOnlineAccountsTimestamp())); @@ -478,4 +478,44 @@ public class BlockTransformer extends Transformer { return signatures; } + public static byte[] encodeOnlineAccountNonces(List nonces) throws TransformationException { + try { + final int length = nonces.size() * Transformer.INT_LENGTH; + ByteArrayOutputStream bytes = new ByteArrayOutputStream(length); + + for (int i = 0; i < nonces.size(); ++i) { + Integer nonce = nonces.get(i); + if (nonce == null || nonce < 0) { + throw new TransformationException("Unable to serialize online account nonces due to invalid value"); + } + bytes.write(Ints.toByteArray(nonce)); + } + + return bytes.toByteArray(); + + } catch (IOException e) { + throw new TransformationException("Unable to serialize online account nonces", e); + } + } + + public static List decodeOnlineAccountNonces(byte[] encodedNonces) { + List nonces = new ArrayList<>(); + + ByteBuffer bytes = ByteBuffer.wrap(encodedNonces); + final int count = encodedNonces.length / Transformer.INT_LENGTH; + + for (int i = 0; i < count; i++) { + Integer nonce = bytes.getInt(); + nonces.add(nonce); + } + + return nonces; + } + + public static byte[] extract(byte[] input, int pos, int length) { + byte[] output = new byte[length]; + System.arraycopy(input, pos, output, 0, length); + return output; + } + } diff --git a/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java b/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java index 69a9ef5b..b966ed2b 100644 --- a/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java +++ b/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java @@ -4,6 +4,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import org.qortal.block.BlockChain; import org.qortal.crypto.Crypto; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.ChatTransactionData; @@ -22,11 +23,13 @@ public class ChatTransactionTransformer extends TransactionTransformer { private static final int NONCE_LENGTH = INT_LENGTH; private static final int HAS_RECIPIENT_LENGTH = BOOLEAN_LENGTH; private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH; + private static final int HAS_CHAT_REFERENCE_LENGTH = BOOLEAN_LENGTH; + private static final int CHAT_REFERENCE_LENGTH = SIGNATURE_LENGTH; private static final int DATA_SIZE_LENGTH = INT_LENGTH; private static final int IS_TEXT_LENGTH = BOOLEAN_LENGTH; private static final int IS_ENCRYPTED_LENGTH = BOOLEAN_LENGTH; - private static final int EXTRAS_LENGTH = NONCE_LENGTH + HAS_RECIPIENT_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH; + private static final int EXTRAS_LENGTH = NONCE_LENGTH + HAS_RECIPIENT_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH + HAS_CHAT_REFERENCE_LENGTH; protected static final TransactionLayout layout; @@ -77,13 +80,24 @@ public class ChatTransactionTransformer extends TransactionTransformer { long fee = byteBuffer.getLong(); + byte[] chatReference = null; + + if (timestamp >= BlockChain.getInstance().getChatReferenceTimestamp()) { + boolean hasChatReference = byteBuffer.get() != 0; + + if (hasChatReference) { + chatReference = new byte[CHAT_REFERENCE_LENGTH]; + byteBuffer.get(chatReference); + } + } + byte[] signature = new byte[SIGNATURE_LENGTH]; byteBuffer.get(signature); BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderPublicKey, fee, signature); String sender = Crypto.toAddress(senderPublicKey); - return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, data, isText, isEncrypted); + return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, chatReference, data, isText, isEncrypted); } public static int getDataLength(TransactionData transactionData) { @@ -94,6 +108,9 @@ public class ChatTransactionTransformer extends TransactionTransformer { if (chatTransactionData.getRecipient() != null) dataLength += RECIPIENT_LENGTH; + if (chatTransactionData.getChatReference() != null) + dataLength += CHAT_REFERENCE_LENGTH; + return dataLength; } @@ -124,6 +141,16 @@ public class ChatTransactionTransformer extends TransactionTransformer { bytes.write(Longs.toByteArray(chatTransactionData.getFee())); + if (transactionData.getTimestamp() >= BlockChain.getInstance().getChatReferenceTimestamp()) { + // Include chat reference if it's not null + if (chatTransactionData.getChatReference() != null) { + bytes.write((byte) 1); + bytes.write(chatTransactionData.getChatReference()); + } else { + bytes.write((byte) 0); + } + } + if (chatTransactionData.getSignature() != null) bytes.write(chatTransactionData.getSignature()); diff --git a/src/main/java/org/qortal/utils/ArbitraryTransactionUtils.java b/src/main/java/org/qortal/utils/ArbitraryTransactionUtils.java index 9b81bd68..4c464bee 100644 --- a/src/main/java/org/qortal/utils/ArbitraryTransactionUtils.java +++ b/src/main/java/org/qortal/utils/ArbitraryTransactionUtils.java @@ -5,7 +5,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.arbitrary.ArbitraryDataFile; import org.qortal.arbitrary.ArbitraryDataFileChunk; +import org.qortal.arbitrary.ArbitraryDataReader; +import org.qortal.arbitrary.ArbitraryDataResource; import org.qortal.arbitrary.misc.Service; +import org.qortal.data.arbitrary.ArbitraryResourceStatus; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.repository.DataException; @@ -410,4 +413,31 @@ public class ArbitraryTransactionUtils { return transactions.stream().skip(offset).limit(limit).collect(Collectors.toList()); } + + /** + * Lookup status of resource + * @param service + * @param name + * @param identifier + * @param build + * @return + */ + public static ArbitraryResourceStatus getStatus(Service service, String name, String identifier, Boolean build) { + + // If "build" has been specified, build the resource before returning its status + if (build != null && build == true) { + ArbitraryDataReader reader = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, null); + try { + if (!reader.isBuilding()) { + reader.loadSynchronously(false); + } + } catch (Exception e) { + // No need to handle exception, as it will be reflected in the status + } + } + + ArbitraryDataResource resource = new ArbitraryDataResource(name, ArbitraryDataFile.ResourceIdType.NAME, service, identifier); + return resource.getStatus(false); + } + } diff --git a/src/main/java/org/qortal/utils/BitTwiddling.java b/src/main/java/org/qortal/utils/BitTwiddling.java index eda5b4f6..72bd83cc 100644 --- a/src/main/java/org/qortal/utils/BitTwiddling.java +++ b/src/main/java/org/qortal/utils/BitTwiddling.java @@ -1,5 +1,7 @@ package org.qortal.utils; +import java.nio.ByteBuffer; + public class BitTwiddling { /** @@ -48,4 +50,25 @@ public class BitTwiddling { | (bytes[start + 4] & 0xffL) << 24 | (bytes[start + 5] & 0xffL) << 16 | (bytes[start + 6] & 0xffL) << 8 | (bytes[start + 7] & 0xffL); } + /** Convert little-endian bytes to long */ + public static long longFromLEBytes(byte[] bytes, int start) { + return (bytes[start] & 0xffL) | (bytes[start + 1] & 0xffL) << 8 | (bytes[start + 2] & 0xffL) << 16 | (bytes[start + 3] & 0xffL) << 24 + | (bytes[start + 4] & 0xffL) << 32 | (bytes[start + 5] & 0xffL) << 40 | (bytes[start + 6] & 0xffL) << 48 | (bytes[start + 7] & 0xffL) << 56; + } + + + /** Read 8-bit unsigned integer from byte buffer */ + public static int readU8(ByteBuffer byteBuffer) { + byte[] sizeBytes = new byte[1]; + byteBuffer.get(sizeBytes); + return sizeBytes[0] & 0xff; + } + + /** Read 32-bit unsigned integer from byte buffer */ + public static int readU32(ByteBuffer byteBuffer) { + byte[] bytes = new byte[4]; + byteBuffer.get(bytes); + return BitTwiddling.intFromLEBytes(bytes, 0); + } + } diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index d2104e65..46b4b4f9 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -15,10 +15,16 @@ "minAccountLevelToMint": 1, "minAccountLevelForBlockSubmissions": 5, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 6, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 1657382400000, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 43200000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 1659801600000, + "selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000, "rewardsByHeight": [ { "height": 1, "reward": 5.00 }, { "height": 259201, "reward": 4.75 }, @@ -34,15 +40,27 @@ { "height": 2851201, "reward": 2.25 }, { "height": 3110401, "reward": 2.00 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1010000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -57,11 +75,21 @@ "atFindNextTransactionFix": 275000, "newBlockSigHeight": 320000, "shareBinFix": 399000, + "sharesByLevelV2Height": 1010000, + "rewardShareLimitTimestamp": 1657382400000, "calcChainWeightTimestamp": 1620579600000, "transactionV5Timestamp": 1642176000000, "transactionV6Timestamp": 9999999999999, - "disableReferenceTimestamp": 1655222400000 + "disableReferenceTimestamp": 1655222400000, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 1092000, + "selfSponsorshipAlgoV1Height": 1092400, + "feeValidationFixTimestamp": 1671918000000, + "chatReferenceTimestamp": 1674316800000 }, + "checkpoints": [ + { "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" } + ], "genesisInfo": { "version": 4, "timestamp": "1593450000000", diff --git a/src/main/resources/i18n/ApiError_ko.properties b/src/main/resources/i18n/ApiError_ko.properties new file mode 100644 index 00000000..4ada1df8 --- /dev/null +++ b/src/main/resources/i18n/ApiError_ko.properties @@ -0,0 +1,83 @@ +#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) +# Keys are from api.ApiError enum + +# "localeLang": "ko", + +### Common ### +JSON = JSON 메시지를 구문 분석하지 못했습니다. + +INSUFFICIENT_BALANCE = 잔고 부족 + +UNAUTHORIZED = 승인되지 않은 API 호출 + +REPOSITORY_ISSUE = 리포지토리 오류 + +NON_PRODUCTION = 이 API 호출은 프로덕션 시스템에 허용되지 않습니다. + +BLOCKCHAIN_NEEDS_SYNC = 블록체인이 먼저 동기화되어야 함 + +NO_TIME_SYNC = 아직 동기화가 없습니다. + +### Validation ### +INVALID_SIGNATURE = 무효 서명 + +INVALID_ADDRESS = 잘못된 주소 + +INVALID_PUBLIC_KEY = 잘못된 공개 키 + +INVALID_DATA = 잘못된 데이터 + +INVALID_NETWORK_ADDRESS = 잘못된 네트워크 주소 + +ADDRESS_UNKNOWN = 계정 주소 알 수 없음 + +INVALID_CRITERIA = 잘못된 검색 기준 + +INVALID_REFERENCE = 무효 참조 + +TRANSFORMATION_ERROR = JSON을 트랜잭션으로 변환할 수 없습니다. + +INVALID_PRIVATE_KEY = 잘못된 개인 키 + +INVALID_HEIGHT = 잘못된 블록 높이 + +CANNOT_MINT = 계정을 만들 수 없습니다. + +### Blocks ### +BLOCK_UNKNOWN = 알 수 없는 블록 + +### Transactions ### +TRANSACTION_UNKNOWN = 알 수 없는 거래 + +PUBLIC_KEY_NOT_FOUND = 공개 키를 찾을 수 없음 + +# this one is special in that caller expected to pass two additional strings, hence the two %s +TRANSACTION_INVALID = 유효하지 않은 거래: %s (%s) + +### Naming ### +NAME_UNKNOWN = 이름 미상 + +### Asset ### +INVALID_ASSET_ID = 잘못된 자산 ID + +INVALID_ORDER_ID = 자산 주문 ID가 잘못되었습니다. + +ORDER_UNKNOWN = 알 수 없는 자산 주문 ID + +### Groups ### +GROUP_UNKNOWN = 알 수 없는 그룹 + +### Foreign Blockchain ### +FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = 외부 블록체인 또는 일렉트럼X 네트워크 문제 + +FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = 외부 블록체인 잔액 부족 + +FOREIGN_BLOCKCHAIN_TOO_SOON = 외부 블록체인 트랜잭션을 브로드캐스트하기에는 너무 빠릅니다(LockTime/중앙 블록 시간). + +### Trade Portal ### +ORDER_SIZE_TOO_SMALL = 주문량이 너무 적다 + +### Data ### +FILE_NOT_FOUND = 파일을 찾을 수 없음 + +NO_REPLY = 피어가 허용된 시간 내에 응답하지 않음 diff --git a/src/main/resources/i18n/ApiError_pl.properties b/src/main/resources/i18n/ApiError_pl.properties new file mode 100644 index 00000000..fcb6191c --- /dev/null +++ b/src/main/resources/i18n/ApiError_pl.properties @@ -0,0 +1,83 @@ +#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) +# Keys are from api.ApiError enum + +# "localeLang": "pl", + +### Common ### +JSON = nie udało się przetworzyć wiadomości JSON + +INSUFFICIENT_BALANCE = niedostateczne środki + +UNAUTHORIZED = nieautoryzowane połączenie API + +REPOSITORY_ISSUE = błąd repozytorium + +NON_PRODUCTION = to wywołanie API nie jest dozwolone dla systemów produkcyjnych + +BLOCKCHAIN_NEEDS_SYNC = blockchain musi się najpierw zsynchronizować + +NO_TIME_SYNC = zegar się jeszcze nie zsynchronizował + +### Validation ### +INVALID_SIGNATURE = nieprawidłowa sygnatura + +INVALID_ADDRESS = nieprawidłowy adres + +INVALID_PUBLIC_KEY = nieprawidłowy klucz publiczny + +INVALID_DATA = nieprawidłowe dane + +INVALID_NETWORK_ADDRESS = nieprawidłowy adres sieci + +ADDRESS_UNKNOWN = nieznany adres konta + +INVALID_CRITERIA = nieprawidłowe kryteria wyszukiwania + +INVALID_REFERENCE = nieprawidłowe skierowanie + +TRANSFORMATION_ERROR = nie udało się przekształcić JSON w transakcję + +INVALID_PRIVATE_KEY = klucz prywatny jest niepoprawny + +INVALID_HEIGHT = nieprawidłowa wysokość bloku + +CANNOT_MINT = konto nie możne bić monet + +### Blocks ### +BLOCK_UNKNOWN = blok nieznany + +### Transactions ### +TRANSACTION_UNKNOWN = nieznana transakcja + +PUBLIC_KEY_NOT_FOUND = nie znaleziono klucza publicznego + +# this one is special in that caller expected to pass two additional strings, hence the two %s +TRANSACTION_INVALID = transakcja nieważna: %s (%s) + +### Naming ### +NAME_UNKNOWN = nazwa nieznana + +### Asset ### +INVALID_ASSET_ID = nieprawidłowy identyfikator aktywy + +INVALID_ORDER_ID = nieprawidłowy identyfikator zlecenia aktywy + +ORDER_UNKNOWN = nieznany identyfikator zlecenia aktywy + +### Groups ### +GROUP_UNKNOWN = nieznana grupa + +### Foreign Blockchain ### +FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = obcy blockchain lub problem z siecią ElectrumX + +FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = niewystarczające środki na obcym blockchainie + +FOREIGN_BLOCKCHAIN_TOO_SOON = zbyt wczesne nadawanie transakcji na obcym blockchainie (okres karencji/średni czas bloku) + +### Trade Portal ### +ORDER_SIZE_TOO_SMALL = zbyt niska kwota zlecenia + +### Data ### +FILE_NOT_FOUND = plik nie został znaleziony + +NO_REPLY = peer nie odpowiedział w wyznaczonym czasie diff --git a/src/main/resources/i18n/ApiError_ru.properties b/src/main/resources/i18n/ApiError_ru.properties index 52580ac8..1367f29b 100644 --- a/src/main/resources/i18n/ApiError_ru.properties +++ b/src/main/resources/i18n/ApiError_ru.properties @@ -16,7 +16,7 @@ NON_PRODUCTION = этот вызов API не разрешен для произ BLOCKCHAIN_NEEDS_SYNC = блокчейн должен сначала синхронизироваться -NO_TIME_SYNC = пока нет синхронизации часов +NO_TIME_SYNC = время не синхронизировано ### Validation ### INVALID_SIGNATURE = недействительная подпись @@ -72,7 +72,7 @@ FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = проблема с внешним блокч FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = недостаточный баланс на внешнем блокчейне -FOREIGN_BLOCKCHAIN_TOO_SOON = слишком рано для трансляции транзакции во внений блокчей (время блокировки/среднее время блока) +FOREIGN_BLOCKCHAIN_TOO_SOON = слишком рано для трансляции транзакции во внешний блокчей (время блокировки/среднее время блока) ### Trade Portal ### ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера @@ -80,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера ### Data ### FILE_NOT_FOUND = файл не найден -NO_REPLY = узел не ответил данными +NO_REPLY = нет ответа diff --git a/src/main/resources/i18n/SysTray_de.properties b/src/main/resources/i18n/SysTray_de.properties index d4abf2a3..7fa041b3 100644 --- a/src/main/resources/i18n/SysTray_de.properties +++ b/src/main/resources/i18n/SysTray_de.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automatisches Update BLOCK_HEIGHT = Blockhöhe +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Entwicklungs-Version CHECK_TIME_ACCURACY = Prüfe Zeitgenauigkeit diff --git a/src/main/resources/i18n/SysTray_en.properties b/src/main/resources/i18n/SysTray_en.properties index 204f0df2..39940be0 100644 --- a/src/main/resources/i18n/SysTray_en.properties +++ b/src/main/resources/i18n/SysTray_en.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Auto Update BLOCK_HEIGHT = height +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Build version CHECK_TIME_ACCURACY = Check time accuracy diff --git a/src/main/resources/i18n/SysTray_es.properties b/src/main/resources/i18n/SysTray_es.properties index d4b931d4..36cbb22c 100644 --- a/src/main/resources/i18n/SysTray_es.properties +++ b/src/main/resources/i18n/SysTray_es.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Actualización automática BLOCK_HEIGHT = altura +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Versión de compilación CHECK_TIME_ACCURACY = Comprobar la precisión del tiempo diff --git a/src/main/resources/i18n/SysTray_fi.properties b/src/main/resources/i18n/SysTray_fi.properties index bc787715..4038d615 100644 --- a/src/main/resources/i18n/SysTray_fi.properties +++ b/src/main/resources/i18n/SysTray_fi.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automaattinen päivitys BLOCK_HEIGHT = korkeus +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Versio CHECK_TIME_ACCURACY = Tarkista ajan tarkkuus diff --git a/src/main/resources/i18n/SysTray_fr.properties b/src/main/resources/i18n/SysTray_fr.properties index 6e60713c..2e376842 100644 --- a/src/main/resources/i18n/SysTray_fr.properties +++ b/src/main/resources/i18n/SysTray_fr.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Mise à jour automatique BLOCK_HEIGHT = hauteur +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Numéro de version CHECK_TIME_ACCURACY = Vérifier l'heure diff --git a/src/main/resources/i18n/SysTray_hu.properties b/src/main/resources/i18n/SysTray_hu.properties index 9bc51ff5..74ab21ac 100644 --- a/src/main/resources/i18n/SysTray_hu.properties +++ b/src/main/resources/i18n/SysTray_hu.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automatikus Frissítés BLOCK_HEIGHT = blokkmagasság +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Verzió CHECK_TIME_ACCURACY = Óra pontosságának ellenőrzése diff --git a/src/main/resources/i18n/SysTray_it.properties b/src/main/resources/i18n/SysTray_it.properties index bf61cc46..d966d825 100644 --- a/src/main/resources/i18n/SysTray_it.properties +++ b/src/main/resources/i18n/SysTray_it.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Aggiornamento automatico BLOCK_HEIGHT = altezza +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Versione CHECK_TIME_ACCURACY = Controlla la precisione dell'ora diff --git a/src/main/resources/i18n/SysTray_ko.properties b/src/main/resources/i18n/SysTray_ko.properties new file mode 100644 index 00000000..dc6cb69b --- /dev/null +++ b/src/main/resources/i18n/SysTray_ko.properties @@ -0,0 +1,48 @@ +#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) +# SysTray pop-up menu + +APPLYING_UPDATE_AND_RESTARTING = 자동 업데이트를 적용하고 다시 시작하는 중... + +AUTO_UPDATE = 자동 업데이트 + +BLOCK_HEIGHT = 높이 + +BLOCKS_REMAINING = blocks remaining + +BUILD_VERSION = 빌드 버전 + +CHECK_TIME_ACCURACY = 시간 정확도 점검 + +CONNECTING = 연결하는 + +CONNECTION = 연결 + +CONNECTIONS = 연결 + +CREATING_BACKUP_OF_DB_FILES = 데이터베이스 파일의 백업을 만드는 중... + +DB_BACKUP = Database Backup + +DB_CHECKPOINT = Database Checkpoint + +DB_MAINTENANCE = 데이터베이스 유지 관리 + +EXIT = 종료 + +LITE_NODE = 라이트 노드 + +MINTING_DISABLED = 민팅중이 아님 + +MINTING_ENABLED = \u2714 민팅 + +OPEN_UI = UI 열기 + +PERFORMING_DB_CHECKPOINT = 커밋되지 않은 데이터베이스 변경 내용을 저장하는 중... + +PERFORMING_DB_MAINTENANCE = 예약된 유지 관리 수행 중... + +SYNCHRONIZE_CLOCK = 시간 동기화 + +SYNCHRONIZING_BLOCKCHAIN = 동기화중 + +SYNCHRONIZING_CLOCK = 시간 동기화 diff --git a/src/main/resources/i18n/SysTray_nl.properties b/src/main/resources/i18n/SysTray_nl.properties index 8a4f112b..c2acb7ce 100644 --- a/src/main/resources/i18n/SysTray_nl.properties +++ b/src/main/resources/i18n/SysTray_nl.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automatische Update BLOCK_HEIGHT = Block hoogte +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Versie nummer CHECK_TIME_ACCURACY = Controleer accuraatheid van de tijd diff --git a/src/main/resources/i18n/SysTray_pl.properties b/src/main/resources/i18n/SysTray_pl.properties new file mode 100644 index 00000000..84740da0 --- /dev/null +++ b/src/main/resources/i18n/SysTray_pl.properties @@ -0,0 +1,46 @@ +#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) +# SysTray pop-up menu + +APPLYING_UPDATE_AND_RESTARTING = Zastosowanie automatycznej aktualizacji i ponowne uruchomienie... + +AUTO_UPDATE = Automatyczna aktualizacja + +BLOCK_HEIGHT = wysokość + +BUILD_VERSION = Wersja kompilacji + +CHECK_TIME_ACCURACY = Sprawdz dokładność czasu + +CONNECTING = Łączenie + +CONNECTION = połączenie + +CONNECTIONS = połączenia + +CREATING_BACKUP_OF_DB_FILES = Tworzenie kopii zapasowej plików bazy danych... + +DB_BACKUP = Kopia zapasowa bazy danych + +DB_CHECKPOINT = Punkt kontrolny bazy danych... + +DB_MAINTENANCE = Konserwacja bazy danych + +EXIT = Zakończ + +LITE_NODE = Lite node + +MINTING_DISABLED = Mennica zamknięta + +MINTING_ENABLED = \u2714 Mennica aktywna + +OPEN_UI = Otwórz interfejs użytkownika + +PERFORMING_DB_CHECKPOINT = Zapisywanie niezaksięgowanych zmian w bazie danych... + +PERFORMING_DB_MAINTENANCE = Performing scheduled maintenance... + +SYNCHRONIZE_CLOCK = Synchronizuj zegar + +SYNCHRONIZING_BLOCKCHAIN = Synchronizacja + +SYNCHRONIZING_CLOCK = Synchronizacja zegara diff --git a/src/main/resources/i18n/SysTray_ro.properties b/src/main/resources/i18n/SysTray_ro.properties index 0e1aa6c6..4130bbcb 100644 --- a/src/main/resources/i18n/SysTray_ro.properties +++ b/src/main/resources/i18n/SysTray_ro.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Actualizare automata BLOCK_HEIGHT = dimensiune +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = versiunea compilatiei CHECK_TIME_ACCURACY = verificare exactitate ora diff --git a/src/main/resources/i18n/SysTray_ru.properties b/src/main/resources/i18n/SysTray_ru.properties index fc3d8648..c8615f73 100644 --- a/src/main/resources/i18n/SysTray_ru.properties +++ b/src/main/resources/i18n/SysTray_ru.properties @@ -1,12 +1,14 @@ #Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) # SysTray pop-up menu -APPLYING_UPDATE_AND_RESTARTING = Применение автоматического обновления и перезапуска... +APPLYING_UPDATE_AND_RESTARTING = Применение автоматического обновления и перезапуск... AUTO_UPDATE = Автоматическое обновление BLOCK_HEIGHT = Высота блока +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Версия сборки CHECK_TIME_ACCURACY = Проверка точного времени diff --git a/src/main/resources/i18n/SysTray_sv.properties b/src/main/resources/i18n/SysTray_sv.properties index 9aec8e9b..96f291b5 100644 --- a/src/main/resources/i18n/SysTray_sv.properties +++ b/src/main/resources/i18n/SysTray_sv.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = Automatisk uppdatering BLOCK_HEIGHT = höjd +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = Byggversion CHECK_TIME_ACCURACY = Kontrollera tidens noggrannhet @@ -25,7 +27,7 @@ DB_CHECKPOINT = Databaskontrollpunkt DB_MAINTENANCE = Databasunderhåll -EXIT = Utgång +EXIT = Avsluta MINTING_DISABLED = Präglar INTE diff --git a/src/main/resources/i18n/SysTray_zh_CN.properties b/src/main/resources/i18n/SysTray_zh_CN.properties index c103d24b..d6848a7c 100644 --- a/src/main/resources/i18n/SysTray_zh_CN.properties +++ b/src/main/resources/i18n/SysTray_zh_CN.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = 自动更新 BLOCK_HEIGHT = 区块高度 +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = 版本 CHECK_TIME_ACCURACY = 检查时间准确性 diff --git a/src/main/resources/i18n/SysTray_zh_TW.properties b/src/main/resources/i18n/SysTray_zh_TW.properties index 5e6ccc3e..eabdbb63 100644 --- a/src/main/resources/i18n/SysTray_zh_TW.properties +++ b/src/main/resources/i18n/SysTray_zh_TW.properties @@ -7,6 +7,8 @@ AUTO_UPDATE = 自動更新 BLOCK_HEIGHT = 區塊高度 +BLOCKS_REMAINING = blocks remaining + BUILD_VERSION = 版本 CHECK_TIME_ACCURACY = 檢查時間準確性 diff --git a/src/main/resources/i18n/TransactionValidity_ko.properties b/src/main/resources/i18n/TransactionValidity_ko.properties new file mode 100644 index 00000000..a12b33f6 --- /dev/null +++ b/src/main/resources/i18n/TransactionValidity_ko.properties @@ -0,0 +1,195 @@ +# + +ACCOUNT_ALREADY_EXISTS = 계정이 이미 존재합니다. + +ACCOUNT_CANNOT_REWARD_SHARE = 계정이 보상을 공유할 수 없습니다. + +ADDRESS_ABOVE_RATE_LIMIT = 주소가 지정된 속도 제한에 도달했습니다. + +ADDRESS_BLOCKED = 이 주소는 차단되었습니다. + +ALREADY_GROUP_ADMIN = 이미 그룹 관리자 + +ALREADY_GROUP_MEMBER = 이미 그룹 맴버 + +ALREADY_VOTED_FOR_THAT_OPTION = 이미 그 옵션에 투표했다. + +ASSET_ALREADY_EXISTS = 자산이 이미 있습니다. + +ASSET_DOES_NOT_EXIST = 자산이 존재하지 않습니다. + +ASSET_DOES_NOT_MATCH_AT = 자산이 AT의 자산과 일치하지 않습니다. + +ASSET_NOT_SPENDABLE = 자산을 사용할 수 없습니다. + +AT_ALREADY_EXISTS = AT가 이미 있습니다. + +AT_IS_FINISHED = AT가 완료되었습니다. + +AT_UNKNOWN = 알 수 없는 AT + +BAN_EXISTS = 금지가 이미 있습니다. + +BAN_UNKNOWN = 금지 알 수 없음 + +BANNED_FROM_GROUP = 그룹에서 금지 + +BUYER_ALREADY_OWNER = 구매자는 이미 소유자입니다 + +CLOCK_NOT_SYNCED = 동기화되지 않은 시간 + +DUPLICATE_MESSAGE = 주소가 중복 메시지를 보냈습니다. + +DUPLICATE_OPTION = 중복 옵션 + +GROUP_ALREADY_EXISTS = 그룹이 이미 존재합니다 + +GROUP_APPROVAL_DECIDED = 그룹 승인이 이미 결정되었습니다. + +GROUP_APPROVAL_NOT_REQUIRED = 그룹 승인이 필요하지 않음 + +GROUP_DOES_NOT_EXIST = 그룹이 존재하지 않습니다 + +GROUP_ID_MISMATCH = 그룹 ID 불일치 + +GROUP_OWNER_CANNOT_LEAVE = 그룹 소유자는 그룹을 나갈 수 없습니다 + +HAVE_EQUALS_WANT = 소유 자산은 원하는 자산과 동일합니다. + +INCORRECT_NONCE = 잘못된 PoW nonce + +INSUFFICIENT_FEE = 부족한 수수료 + +INVALID_ADDRESS = 잘못된 주소 + +INVALID_AMOUNT = 유효하지 않은 금액 + +INVALID_ASSET_OWNER = 잘못된 자산 소유자 + +INVALID_AT_TRANSACTION = 유효하지 않은 AT 거래 + +INVALID_AT_TYPE_LENGTH = 잘못된 AT '유형' 길이 + +INVALID_BUT_OK = 유효하지 않지만 OK + +INVALID_CREATION_BYTES = 잘못된 생성 바이트 + +INVALID_DATA_LENGTH = 잘못된 데이터 길이 + +INVALID_DESCRIPTION_LENGTH = 잘못된 설명 길이 + +INVALID_GROUP_APPROVAL_THRESHOLD = 잘못된 그룹 승인 임계값 + +INVALID_GROUP_BLOCK_DELAY = 잘못된 그룹 승인 차단 지연 + +INVALID_GROUP_ID = 잘못된 그룹 ID + +INVALID_GROUP_OWNER = 잘못된 그룹 소유자 + +INVALID_LIFETIME = 유효하지 않은 수명 + +INVALID_NAME_LENGTH = 잘못된 이름 길이 + +INVALID_NAME_OWNER = 잘못된 이름 소유자 + +INVALID_OPTION_LENGTH = 잘못된 옵션 길이 + +INVALID_OPTIONS_COUNT = 잘못된 옵션 수 + +INVALID_ORDER_CREATOR = 잘못된 주문 생성자 + +INVALID_PAYMENTS_COUNT = 유효하지 않은 지불 수 + +INVALID_PUBLIC_KEY = 잘못된 공개 키 + +INVALID_QUANTITY = 유효하지 않은 수량 + +INVALID_REFERENCE = 잘못된 참조 + +INVALID_RETURN = 무효 반환 + +INVALID_REWARD_SHARE_PERCENT = 잘못된 보상 공유 비율 + +INVALID_SELLER = 무효 판매자 + +INVALID_TAGS_LENGTH = invalid 'tags' length + +INVALID_TIMESTAMP_SIGNATURE = 유효하지 않은 타임스탬프 서명 + +INVALID_TX_GROUP_ID = 잘못된 트랜잭션 그룹 ID + +INVALID_VALUE_LENGTH = 잘못된 '값' 길이 + +INVITE_UNKNOWN = 알 수 없는 그룹 초대 + +JOIN_REQUEST_EXISTS = 그룹 가입 요청이 이미 있습니다. + +MAXIMUM_REWARD_SHARES = 이미 이 계정에 대한 최대 보상 공유 수에 도달했습니다.t + +MISSING_CREATOR = 실종된 창작자 + +MULTIPLE_NAMES_FORBIDDEN = 계정당 여러 등록 이름은 금지되어 있습니다. + +NAME_ALREADY_FOR_SALE = 이미 판매 중인 이름 + +NAME_ALREADY_REGISTERED = 이미 등록된 이름 + +NAME_BLOCKED = 이 이름은 차단되었습니다 + +NAME_DOES_NOT_EXIST = 이름이 존재하지 않습니다 + +NAME_NOT_FOR_SALE = 이름은 판매용이 아닙니다 + +NAME_NOT_NORMALIZED = 유니코드 '정규화된' 형식이 아닌 이름 + +NEGATIVE_AMOUNT = 유효하지 않은/음수 금액 + +NEGATIVE_FEE = 무효/음수 수수료 + +NEGATIVE_PRICE = 유효하지 않은/음수 가격 + +NO_BALANCE = 잔액 불충분 + +NO_BLOCKCHAIN_LOCK = 노드의 블록체인이 현재 사용 중입니다. + +NO_FLAG_PERMISSION = 계정에 해당 권한이 없습니다 + +NOT_GROUP_ADMIN = 계정은 그룹 관리자가 아닙니다. + +NOT_GROUP_MEMBER = 계정이 그룹 구성원이 아닙니다. + +NOT_MINTING_ACCOUNT = 계정은 발행할 수 없습니다 + +NOT_YET_RELEASED = 아직 출시되지 않은 기능 + +OK = OK + +ORDER_ALREADY_CLOSED = 아직 출시되지 않은 기능 + +ORDER_DOES_NOT_EXIST = 자산 거래 주문이 존재하지 않습니다 + +POLL_ALREADY_EXISTS = 설문조사가 이미 존재합니다 + +POLL_DOES_NOT_EXIST = 설문조사가 존재하지 않습니다 + +POLL_OPTION_DOES_NOT_EXIST = 투표 옵션이 존재하지 않습니다 + +PUBLIC_KEY_UNKNOWN = 공개 키 알 수 없음 + +REWARD_SHARE_UNKNOWN = 알 수 없는 보상 공유 + +SELF_SHARE_EXISTS = 자체 공유(보상 공유)가 이미 존재합니다. + +TIMESTAMP_TOO_NEW = 타임스탬프가 너무 새롭습니다. + +TIMESTAMP_TOO_OLD = 너무 오래된 타임스탬프 + +TOO_MANY_UNCONFIRMED = 계정에 보류 중인 확인되지 않은 거래가 너무 많습니다. + +TRANSACTION_ALREADY_CONFIRMED = 거래가 이미 확인되었습니다 + +TRANSACTION_ALREADY_EXISTS = 거래가 이미 존재합니다 + +TRANSACTION_UNKNOWN = 알 수 없는 거래 + +TX_GROUP_ID_MISMATCH = 트랜잭션의 그룹 ID가 일치하지 않습니다 diff --git a/src/main/resources/i18n/TransactionValidity_pl.properties b/src/main/resources/i18n/TransactionValidity_pl.properties new file mode 100644 index 00000000..bcdceb6e --- /dev/null +++ b/src/main/resources/i18n/TransactionValidity_pl.properties @@ -0,0 +1,196 @@ +# + +ACCOUNT_ALREADY_EXISTS = konto już istnieje + +ACCOUNT_CANNOT_REWARD_SHARE = konto nie może udostępniać nagród + +ADDRESS_ABOVE_RATE_LIMIT = adres osiągnął określony limit stawki + +ADDRESS_BLOCKED = ten adres jest zablokowany + +ALREADY_GROUP_ADMIN = już adminem grupy + +ALREADY_GROUP_MEMBER = już członkiem grupy + +ALREADY_VOTED_FOR_THAT_OPTION = już zagłosowano na ta opcje + +ASSET_ALREADY_EXISTS = aktywa już istnieje + +ASSET_DOES_NOT_EXIST = aktywa nie istnieje + +ASSET_DOES_NOT_MATCH_AT = aktywa nie pasuje do aktywy AT + +ASSET_NOT_SPENDABLE = aktywa nie jest rozporządzalna + +AT_ALREADY_EXISTS = AT już istnieje + +AT_IS_FINISHED = AT zakończył + +AT_UNKNOWN = AT nieznany + +BAN_EXISTS = ban już istnieje + +BAN_UNKNOWN = ban nieznany + +BANNED_FROM_GROUP = zbanowany z grupy + +BUYER_ALREADY_OWNER = kupca jest już właścicielem + +CLOCK_NOT_SYNCED = zegar nie zsynchronizowany + +DUPLICATE_MESSAGE = adres wysłał duplikat wiadomości + +DUPLICATE_OPTION = duplikat opcji + +GROUP_ALREADY_EXISTS = grupa już istnieje + +GROUP_APPROVAL_DECIDED = zatwierdzenie grupy już zdecydowano + +GROUP_APPROVAL_NOT_REQUIRED = zatwierdzenie grupy nie jest wymagane + +GROUP_DOES_NOT_EXIST = grupa nie istnieje + +GROUP_ID_MISMATCH = niedopasowanie identyfikatora grupy + +GROUP_OWNER_CANNOT_LEAVE = właściciel grupy nie może opuścić grupy + +HAVE_EQUALS_WANT = posiadana aktywa równa się chcianej aktywie + +INCORRECT_NONCE = nieprawidłowy nonce PoW + +INSUFFICIENT_FEE = niewystarczająca opłata + +INVALID_ADDRESS = nieprawidłowy adres + +INVALID_AMOUNT = nieprawidłowa kwota + +INVALID_ASSET_OWNER = nieprawidłowy właściciel aktywów + +INVALID_AT_TRANSACTION = nieważna transakcja AT + +INVALID_AT_TYPE_LENGTH = nieprawidłowa długość typu AT + +INVALID_BUT_OK = nieważne, ale OK + +INVALID_CREATION_BYTES = nieprawidłowe bajty tworzenia + +INVALID_DATA_LENGTH = nieprawidłowa długość danych + +INVALID_DESCRIPTION_LENGTH = nieprawidłowa długość opisu + +INVALID_GROUP_APPROVAL_THRESHOLD = nieprawidłowy próg zatwierdzenia grupy + +INVALID_GROUP_BLOCK_DELAY = nieprawidłowe opóźnienie bloku zatwierdzenia grupy + +INVALID_GROUP_ID = nieprawidłowy identyfikator grupy + +INVALID_GROUP_OWNER = nieprawidłowy właściciel grupy + +INVALID_LIFETIME = nieprawidłowy czas istnienia + +INVALID_NAME_LENGTH = nieprawidłowa długość nazwy + +INVALID_NAME_OWNER = nieprawidłowy właściciel nazwy + +INVALID_OPTION_LENGTH = nieprawidłowa długość opcji + +INVALID_OPTIONS_COUNT = nieprawidłowa liczba opcji + +INVALID_ORDER_CREATOR = nieprawidłowy twórca zlecenia + +INVALID_PAYMENTS_COUNT = nieprawidłowa liczba płatności + +INVALID_PUBLIC_KEY = nieprawidłowy klucz publiczny + +INVALID_QUANTITY = nieprawidłowa ilość + +INVALID_REFERENCE = nieprawidłowe skierowanie + +INVALID_RETURN = nieprawidłowy zwrot + +INVALID_REWARD_SHARE_PERCENT = nieprawidłowy procent udziału w nagrodzie + +INVALID_SELLER = nieprawidłowy sprzedawca + +INVALID_TAGS_LENGTH = nieprawidłowa długość tagów + +INVALID_TIMESTAMP_SIGNATURE = nieprawidłowa sygnatura znacznika czasu + +INVALID_TX_GROUP_ID = nieprawidłowy identyfikator grupy transakcji + +INVALID_VALUE_LENGTH = nieprawidłowa długość wartości + +INVITE_UNKNOWN = zaproszenie do grupy nieznane + +JOIN_REQUEST_EXISTS = wniosek o dołączenie do grupy już istnieje + +MAXIMUM_REWARD_SHARES = osiągnięto już maksymalną liczbę udziałów w nagrodzie dla tego konta + +MISSING_CREATOR = brak twórcy + +MULTIPLE_NAMES_FORBIDDEN = zabronione jest używanie wielu nazw na jednym koncie + +NAME_ALREADY_FOR_SALE = nazwa już wystawiona na sprzedaż + +NAME_ALREADY_REGISTERED = nazwa już zarejestrowana + +NAME_BLOCKED = ta nazwa jest zablokowana + +NAME_DOES_NOT_EXIST = nazwa nie istnieje + +NAME_NOT_FOR_SALE = nazwa nie jest przeznaczona do sprzedaży + +NAME_NOT_NORMALIZED = nazwa nie jest w formie 'znormalizowanej' Unicode + +NEGATIVE_AMOUNT = nieprawidłowa/ujemna kwota + +NEGATIVE_FEE = nieprawidłowa/ujemna opłata + +NEGATIVE_PRICE = nieprawidłowa/ujemna cena + +NO_BALANCE = niewystarczające środki + +NO_BLOCKCHAIN_LOCK = węzeł blockchain jest obecnie zajęty + +NO_FLAG_PERMISSION = konto nie ma tego uprawnienia + +NOT_GROUP_ADMIN = konto nie jest adminem grupy + +NOT_GROUP_MEMBER = konto nie jest członkiem grupy + +NOT_MINTING_ACCOUNT = konto nie może bić monet + +NOT_YET_RELEASED = funkcja nie została jeszcze udostępniona + +OK = OK + +ORDER_ALREADY_CLOSED = zlecenie handlu aktywami jest już zakończone + +ORDER_DOES_NOT_EXIST = zlecenie sprzedaży aktywów nie istnieje + +POLL_ALREADY_EXISTS = ankieta już istnieje + +POLL_DOES_NOT_EXIST = ankieta nie istnieje + +POLL_OPTION_DOES_NOT_EXIST = opcja ankiety nie istnieje + +PUBLIC_KEY_UNKNOWN = klucz publiczny nieznany + +REWARD_SHARE_UNKNOWN = nieznany udział w nagrodzie + +SELF_SHARE_EXISTS = samoudział (udział w nagrodzie) już istnieje + +TIMESTAMP_TOO_NEW = zbyt nowy znacznik czasu + +TIMESTAMP_TOO_OLD = zbyt stary znacznik czasu + +TOO_MANY_UNCONFIRMED = rachunek ma zbyt wiele niepotwierdzonych transakcji w toku + +TRANSACTION_ALREADY_CONFIRMED = transakcja została już potwierdzona + +TRANSACTION_ALREADY_EXISTS = transakcja już istnieje + +TRANSACTION_UNKNOWN = transakcja nieznana + +TX_GROUP_ID_MISMATCH = niezgodność ID grupy transakcji + diff --git a/src/main/resources/proto/zcash/compact_formats.proto b/src/main/resources/proto/zcash/compact_formats.proto new file mode 100644 index 00000000..3ac774e7 --- /dev/null +++ b/src/main/resources/proto/zcash/compact_formats.proto @@ -0,0 +1,57 @@ +// Copyright (c) 2019-2020 The Zcash developers +// Copyright (c) 2019-2021 Pirate Chain developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "lightwalletd/walletrpc"; +option swift_prefix = ""; +// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value. +// bytes fields of hashes are in canonical little-endian format. + +// CompactBlock is a packaging of ONLY the data from a block that's needed to: +// 1. Detect a payment to your shielded Sapling address +// 2. Detect a spend of your shielded Sapling notes +// 3. Update your witnesses to generate new Sapling spend proofs. +message CompactBlock { + uint32 protoVersion = 1; // the version of this wire format, for storage + uint64 height = 2; // the height of this block + bytes hash = 3; // the ID (hash) of this block, same as in block explorers + bytes prevHash = 4; // the ID (hash) of this block's predecessor + uint32 time = 5; // Unix epoch time when the block was mined + bytes header = 6; // (hash, prevHash, and time) OR (full header) + repeated CompactTx vtx = 7; // zero or more compact transactions from this block +} + +// CompactTx contains the minimum information for a wallet to know if this transaction +// is relevant to it (either pays to it or spends from it) via shielded elements +// only. This message will not encode a transparent-to-transparent transaction. +message CompactTx { + uint64 index = 1; // the index within the full block + bytes hash = 2; // the ID (hash) of this transaction, same as in block explorers + + // The transaction fee: present if server can provide. In the case of a + // stateless server and a transaction with transparent inputs, this will be + // unset because the calculation requires reference to prior transactions. + // in a pure-Sapling context, the fee will be calculable as: + // valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut)) + uint32 fee = 3; + + repeated CompactSpend spends = 4; // inputs + repeated CompactOutput outputs = 5; // outputs +} + +// CompactSpend is a Sapling Spend Description as described in 7.3 of the Zcash +// protocol specification. +message CompactSpend { + bytes nf = 1; // nullifier (see the Zcash protocol specification) +} + +// output is a Sapling Output Description as described in section 7.4 of the +// Zcash protocol spec. Total size is 948. +message CompactOutput { + bytes cmu = 1; // note commitment u-coordinate + bytes epk = 2; // ephemeral public key + bytes ciphertext = 3; // ciphertext and zkproof +} diff --git a/src/main/resources/proto/zcash/darkside.proto b/src/main/resources/proto/zcash/darkside.proto new file mode 100644 index 00000000..e2e03413 --- /dev/null +++ b/src/main/resources/proto/zcash/darkside.proto @@ -0,0 +1,117 @@ +// Copyright (c) 2019-2020 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "lightwalletd/walletrpc"; +option swift_prefix = ""; +import "service.proto"; + +message DarksideMetaState { + int32 saplingActivation = 1; + string branchID = 2; + string chainName = 3; +} + +// A block is a hex-encoded string. +message DarksideBlock { + string block = 1; +} + +// DarksideBlocksURL is typically something like: +// https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt +message DarksideBlocksURL { + string url = 1; +} + +// DarksideTransactionsURL refers to an HTTP source that contains a list +// of hex-encoded transactions, one per line, that are to be associated +// with the given height (fake-mined into the block at that height) +message DarksideTransactionsURL { + int32 height = 1; + string url = 2; +} + +message DarksideHeight { + int32 height = 1; +} + +message DarksideEmptyBlocks { + int32 height = 1; + int32 nonce = 2; + int32 count = 3; +} + +// Darksidewalletd maintains two staging areas, blocks and transactions. The +// Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything +// in the staging area to the working (operational) state that the mock zcashd +// serves; transactions are placed into their corresponding blocks (by height). +service DarksideStreamer { + // Reset reverts all darksidewalletd state (active block range, latest height, + // staged blocks and transactions) and lightwalletd state (cache) to empty, + // the same as the initial state. This occurs synchronously and instantaneously; + // no reorg happens in lightwalletd. This is good to do before each independent + // test so that no state leaks from one test to another. + // Also sets (some of) the values returned by GetLightdInfo(). The Sapling + // activation height specified here must be where the block range starts. + rpc Reset(DarksideMetaState) returns (Empty) {} + + // StageBlocksStream accepts a list of blocks and saves them into the blocks + // staging area until ApplyStaged() is called; there is no immediate effect on + // the mock zcashd. Blocks are hex-encoded. Order is important, see ApplyStaged. + rpc StageBlocksStream(stream DarksideBlock) returns (Empty) {} + + // StageBlocks is the same as StageBlocksStream() except the blocks are fetched + // from the given URL. Blocks are one per line, hex-encoded (not JSON). + rpc StageBlocks(DarksideBlocksURL) returns (Empty) {} + + // StageBlocksCreate is like the previous two, except it creates 'count' + // empty blocks at consecutive heights starting at height 'height'. The + // 'nonce' is part of the header, so it contributes to the block hash; this + // lets you create identical blocks (same transactions and height), but with + // different hashes. + rpc StageBlocksCreate(DarksideEmptyBlocks) returns (Empty) {} + + // StageTransactionsStream stores the given transaction-height pairs in the + // staging area until ApplyStaged() is called. Note that these transactions + // are not returned by the production GetTransaction() gRPC until they + // appear in a "mined" block (contained in the active blockchain presented + // by the mock zcashd). + rpc StageTransactionsStream(stream RawTransaction) returns (Empty) {} + + // StageTransactions is the same except the transactions are fetched from + // the given url. They are all staged into the block at the given height. + // Staging transactions to different heights requires multiple calls. + rpc StageTransactions(DarksideTransactionsURL) returns (Empty) {} + + // ApplyStaged iterates the list of blocks that were staged by the + // StageBlocks*() gRPCs, in the order they were staged, and "merges" each + // into the active, working blocks list that the mock zcashd is presenting + // to lightwalletd. Even as each block is applied, the active list can't + // have gaps; if the active block range is 1000-1006, and the staged block + // range is 1003-1004, the resulting range is 1000-1004, with 1000-1002 + // unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped. + // + // After merging all blocks, ApplyStaged() appends staged transactions (in + // the order received) into each one's corresponding (by height) block + // The staging area is then cleared. + // + // The argument specifies the latest block height that mock zcashd reports + // (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can + // also be used to simply advance the latest block height presented by mock + // zcashd. That is, there doesn't need to be anything in the staging area. + rpc ApplyStaged(DarksideHeight) returns (Empty) {} + + // Calls to the production gRPC SendTransaction() store the transaction in + // a separate area (not the staging area); this method returns all transactions + // in this separate area, which is then cleared. The height returned + // with each transaction is -1 (invalid) since these transactions haven't + // been mined yet. The intention is that the transactions returned here can + // then, for example, be given to StageTransactions() to get them "mined" + // into a specified block on the next ApplyStaged(). + rpc GetIncomingTransactions(Empty) returns (stream RawTransaction) {} + + // Clear the incoming transaction pool. + rpc ClearIncomingTransactions(Empty) returns (Empty) {} +} diff --git a/src/main/resources/proto/zcash/service.proto b/src/main/resources/proto/zcash/service.proto new file mode 100644 index 00000000..74230bcd --- /dev/null +++ b/src/main/resources/proto/zcash/service.proto @@ -0,0 +1,181 @@ +// Copyright (c) 2019-2020 The Zcash developers +// Copyright (c) 2019-2021 Pirate Chain developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "lightwalletd/walletrpc"; +option swift_prefix = ""; +import "compact_formats.proto"; + +// A BlockID message contains identifiers to select a block: a height or a +// hash. Specification by hash is not implemented, but may be in the future. +message BlockID { + uint64 height = 1; + bytes hash = 2; +} + +// BlockRange specifies a series of blocks from start to end inclusive. +// Both BlockIDs must be heights; specification by hash is not yet supported. +message BlockRange { + BlockID start = 1; + BlockID end = 2; +} + +// A TxFilter contains the information needed to identify a particular +// transaction: either a block and an index, or a direct transaction hash. +// Currently, only specification by hash is supported. +message TxFilter { + BlockID block = 1; // block identifier, height or hash + uint64 index = 2; // index within the block + bytes hash = 3; // transaction ID (hash, txid) +} + +// RawTransaction contains the complete transaction data. It also optionally includes +// the block height in which the transaction was included. +message RawTransaction { + bytes data = 1; // exact data returned by Zcash 'getrawtransaction' + uint64 height = 2; // height that the transaction was mined (or -1) +} + +// A SendResponse encodes an error code and a string. It is currently used +// only by SendTransaction(). If error code is zero, the operation was +// successful; if non-zero, it and the message specify the failure. +message SendResponse { + int32 errorCode = 1; + string errorMessage = 2; +} + +// Chainspec is a placeholder to allow specification of a particular chain fork. +message ChainSpec {} + +// Empty is for gRPCs that take no arguments, currently only GetLightdInfo. +message Empty {} + +// LightdInfo returns various information about this lightwalletd instance +// and the state of the blockchain. +message LightdInfo { + string version = 1; + string vendor = 2; + bool taddrSupport = 3; // true + string chainName = 4; // either "main" or "test" + uint64 saplingActivationHeight = 5; // depends on mainnet or testnet + string consensusBranchId = 6; // protocol identifier, see consensus/upgrades.cpp + uint64 blockHeight = 7; // latest block on the best chain + string gitCommit = 8; + string branch = 9; + string buildDate = 10; + string buildUser = 11; + uint64 estimatedHeight = 12; // less than tip height if pirated is syncing + string piratedBuild = 13; // example: "v4.1.1-877212414" + string piratedSubversion = 14; // example: "/MagicBean:4.1.1/" +} + +// TransparentAddressBlockFilter restricts the results to the given address +// or block range. +message TransparentAddressBlockFilter { + string address = 1; // t-address + BlockRange range = 2; // start, end heights +} + +// Duration is currently used only for testing, so that the Ping rpc +// can simulate a delay, to create many simultaneous connections. Units +// are microseconds. +message Duration { + int64 intervalUs = 1; +} + +// PingResponse is used to indicate concurrency, how many Ping rpcs +// are executing upon entry and upon exit (after the delay). +// This rpc is used for testing only. +message PingResponse { + int64 entry = 1; + int64 exit = 2; +} + +message Address { + string address = 1; +} +message AddressList { + repeated string addresses = 1; +} +message Balance { + int64 valueZat = 1; +} + +message Exclude { + repeated bytes txid = 1; +} + +// The TreeState is derived from the Zcash z_gettreestate rpc. +message TreeState { + string network = 1; // "main" or "test" + uint64 height = 2; + string hash = 3; // block id + uint32 time = 4; // Unix epoch time when the block was mined + string tree = 5; // sapling commitment tree state +} + +// Results are sorted by height, which makes it easy to issue another +// request that picks up from where the previous left off. +message GetAddressUtxosArg { + repeated string addresses = 1; + uint64 startHeight = 2; + uint32 maxEntries = 3; // zero means unlimited +} +message GetAddressUtxosReply { + string address = 6; + bytes txid = 1; + int32 index = 2; + bytes script = 3; + int64 valueZat = 4; + uint64 height = 5; +} +message GetAddressUtxosReplyList { + repeated GetAddressUtxosReply addressUtxos = 1; +} + +service CompactTxStreamer { + // Return the height of the tip of the best chain + rpc GetLatestBlock(ChainSpec) returns (BlockID) {} + // Return the compact block corresponding to the given block identifier + rpc GetBlock(BlockID) returns (CompactBlock) {} + // Return a list of consecutive compact blocks + rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {} + + // Return the requested full (not compact) transaction (as from pirated) + rpc GetTransaction(TxFilter) returns (RawTransaction) {} + // Submit the given transaction to the Zcash network + rpc SendTransaction(RawTransaction) returns (SendResponse) {} + + // Return the txids corresponding to the given t-address within the given block range + rpc GetTaddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} + rpc GetTaddressBalance(AddressList) returns (Balance) {} + rpc GetTaddressBalanceStream(stream Address) returns (Balance) {} + + // Return the compact transactions currently in the mempool; the results + // can be a few seconds out of date. If the Exclude list is empty, return + // all transactions; otherwise return all *except* those in the Exclude list + // (if any); this allows the client to avoid receiving transactions that it + // already has (from an earlier call to this rpc). The transaction IDs in the + // Exclude list can be shortened to any number of bytes to make the request + // more bandwidth-efficient; if two or more transactions in the mempool + // match a shortened txid, they are all sent (none is excluded). Transactions + // in the exclude list that don't exist in the mempool are ignored. + rpc GetMempoolTx(Exclude) returns (stream CompactTx) {} + + // GetTreeState returns the note commitment tree state corresponding to the given block. + // See section 3.7 of the Zcash protocol specification. It returns several other useful + // values also (even though they can be obtained using GetBlock). + // The block can be specified by either height or hash. + rpc GetTreeState(BlockID) returns (TreeState) {} + + rpc GetAddressUtxos(GetAddressUtxosArg) returns (GetAddressUtxosReplyList) {} + rpc GetAddressUtxosStream(GetAddressUtxosArg) returns (stream GetAddressUtxosReply) {} + + // Return information about this lightwalletd instance and the blockchain + rpc GetLightdInfo(Empty) returns (LightdInfo) {} + // Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production) + rpc Ping(Duration) returns (PingResponse) {} +} diff --git a/src/test/java/org/qortal/test/BlockArchiveTests.java b/src/test/java/org/qortal/test/BlockArchiveTests.java index 32fd0283..3bfa4e84 100644 --- a/src/test/java/org/qortal/test/BlockArchiveTests.java +++ b/src/test/java/org/qortal/test/BlockArchiveTests.java @@ -361,7 +361,7 @@ public class BlockArchiveTests extends Common { assertEquals(0, repository.getBlockArchiveRepository().getBlockArchiveHeight()); // Write blocks 2-900 to the archive (using bulk method) - int fileSizeTarget = 425000; // Pre-calculated size of 900 blocks + int fileSizeTarget = 428600; // Pre-calculated size of 900 blocks assertTrue(HSQLDBDatabaseArchiving.buildBlockArchive(repository, fileSizeTarget)); // Ensure the block archive height has increased @@ -455,7 +455,7 @@ public class BlockArchiveTests extends Common { assertEquals(0, repository.getBlockArchiveRepository().getBlockArchiveHeight()); // Write blocks 2-900 to the archive (using bulk method) - int fileSizeTarget = 42000; // Pre-calculated size of approx 90 blocks + int fileSizeTarget = 42360; // Pre-calculated size of approx 90 blocks assertTrue(HSQLDBDatabaseArchiving.buildBlockArchive(repository, fileSizeTarget)); // Ensure 10 archive files have been created diff --git a/src/test/java/org/qortal/test/CryptoTests.java b/src/test/java/org/qortal/test/CryptoTests.java index 6a0133d2..2cc73182 100644 --- a/src/test/java/org/qortal/test/CryptoTests.java +++ b/src/test/java/org/qortal/test/CryptoTests.java @@ -4,7 +4,7 @@ import org.junit.Test; import org.qortal.account.PrivateKeyAccount; import org.qortal.block.BlockChain; import org.qortal.crypto.AES; -import org.qortal.crypto.BouncyCastle25519; +import org.qortal.crypto.Qortal25519Extras; import org.qortal.crypto.Crypto; import org.qortal.test.common.Common; import org.qortal.utils.Base58; @@ -123,14 +123,14 @@ public class CryptoTests extends Common { random.nextBytes(ed25519PrivateKey); PrivateKeyAccount account = new PrivateKeyAccount(null, ed25519PrivateKey); - byte[] x25519PrivateKey = BouncyCastle25519.toX25519PrivateKey(account.getPrivateKey()); + byte[] x25519PrivateKey = Qortal25519Extras.toX25519PrivateKey(account.getPrivateKey()); X25519PrivateKeyParameters x25519PrivateKeyParams = new X25519PrivateKeyParameters(x25519PrivateKey, 0); // Derive X25519 public key from X25519 private key byte[] x25519PublicKeyFromPrivate = x25519PrivateKeyParams.generatePublicKey().getEncoded(); // Derive X25519 public key from Ed25519 public key - byte[] x25519PublicKeyFromEd25519 = BouncyCastle25519.toX25519PublicKey(account.getPublicKey()); + byte[] x25519PublicKeyFromEd25519 = Qortal25519Extras.toX25519PublicKey(account.getPublicKey()); assertEquals(String.format("Public keys do not match, from private key %s", Base58.encode(ed25519PrivateKey)), Base58.encode(x25519PublicKeyFromPrivate), Base58.encode(x25519PublicKeyFromEd25519)); } @@ -162,10 +162,10 @@ public class CryptoTests extends Common { } private static byte[] calcBCSharedSecret(byte[] ed25519PrivateKey, byte[] ed25519PublicKey) { - byte[] x25519PrivateKey = BouncyCastle25519.toX25519PrivateKey(ed25519PrivateKey); + byte[] x25519PrivateKey = Qortal25519Extras.toX25519PrivateKey(ed25519PrivateKey); X25519PrivateKeyParameters privateKeyParams = new X25519PrivateKeyParameters(x25519PrivateKey, 0); - byte[] x25519PublicKey = BouncyCastle25519.toX25519PublicKey(ed25519PublicKey); + byte[] x25519PublicKey = Qortal25519Extras.toX25519PublicKey(ed25519PublicKey); X25519PublicKeyParameters publicKeyParams = new X25519PublicKeyParameters(x25519PublicKey, 0); byte[] sharedSecret = new byte[32]; @@ -186,10 +186,10 @@ public class CryptoTests extends Common { final String expectedTheirX25519PublicKey = "ANjnZLRSzW9B1aVamiYGKP3XtBooU9tGGDjUiibUfzp2"; final String expectedSharedSecret = "DTMZYG96x8XZuGzDvHFByVLsXedimqtjiXHhXPVe58Ap"; - byte[] ourX25519PrivateKey = BouncyCastle25519.toX25519PrivateKey(ourPrivateKey); + byte[] ourX25519PrivateKey = Qortal25519Extras.toX25519PrivateKey(ourPrivateKey); assertEquals("X25519 private key incorrect", expectedOurX25519PrivateKey, Base58.encode(ourX25519PrivateKey)); - byte[] theirX25519PublicKey = BouncyCastle25519.toX25519PublicKey(theirPublicKey); + byte[] theirX25519PublicKey = Qortal25519Extras.toX25519PublicKey(theirPublicKey); assertEquals("X25519 public key incorrect", expectedTheirX25519PublicKey, Base58.encode(theirX25519PublicKey)); byte[] sharedSecret = calcBCSharedSecret(ourPrivateKey, theirPublicKey); diff --git a/src/test/java/org/qortal/test/SchnorrTests.java b/src/test/java/org/qortal/test/SchnorrTests.java new file mode 100644 index 00000000..e0d1f1c9 --- /dev/null +++ b/src/test/java/org/qortal/test/SchnorrTests.java @@ -0,0 +1,168 @@ +package org.qortal.test; + +import com.google.common.hash.HashCode; +import com.google.common.primitives.Bytes; +import com.google.common.primitives.Longs; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; +import org.junit.Test; +import org.qortal.crypto.Qortal25519Extras; +import org.qortal.data.network.OnlineAccountData; +import org.qortal.test.common.AccountUtils; +import org.qortal.transform.Transformer; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.security.Security; +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + +public class SchnorrTests extends Qortal25519Extras { + + static { + // This must go before any calls to LogManager/Logger + System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); + } + + @Test + public void testConversion() { + // Scalar form + byte[] scalarA = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes(); + System.out.printf("a: %s%n", HashCode.fromBytes(scalarA)); + + byte[] pointA = HashCode.fromString("5866666666666666666666666666666666666666666666666666666666666666".toLowerCase()).asBytes(); + + BigInteger expectedY = new BigInteger("46316835694926478169428394003475163141307993866256225615783033603165251855960"); + + PointAccum pointAccum = Qortal25519Extras.newPointAccum(); + scalarMultBase(scalarA, pointAccum); + + byte[] encoded = new byte[POINT_BYTES]; + if (0 == encodePoint(pointAccum, encoded, 0)) + fail("Point encoding failed"); + + System.out.printf("aG: %s%n", HashCode.fromBytes(encoded)); + assertArrayEquals(pointA, encoded); + + byte[] yBytes = new byte[POINT_BYTES]; + System.arraycopy(encoded,0, yBytes, 0, encoded.length); + Bytes.reverse(yBytes); + + System.out.printf("yBytes: %s%n", HashCode.fromBytes(yBytes)); + BigInteger yBI = new BigInteger(yBytes); + + System.out.printf("aG y: %s%n", yBI); + assertEquals(expectedY, yBI); + } + + @Test + public void testAddition() { + /* + * 1G: b'5866666666666666666666666666666666666666666666666666666666666666' + * 2G: b'c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022' + * 3G: b'd4b4f5784868c3020403246717ec169ff79e26608ea126a1ab69ee77d1b16712' + */ + + // Scalar form + byte[] s1 = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes(); + byte[] s2 = HashCode.fromString("0200000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes(); + + // Point form + byte[] g1 = HashCode.fromString("5866666666666666666666666666666666666666666666666666666666666666".toLowerCase()).asBytes(); + byte[] g2 = HashCode.fromString("c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022".toLowerCase()).asBytes(); + byte[] g3 = HashCode.fromString("d4b4f5784868c3020403246717ec169ff79e26608ea126a1ab69ee77d1b16712".toLowerCase()).asBytes(); + + PointAccum p1 = Qortal25519Extras.newPointAccum(); + scalarMultBase(s1, p1); + + PointAccum p2 = Qortal25519Extras.newPointAccum(); + scalarMultBase(s2, p2); + + pointAdd(pointCopy(p1), p2); + + byte[] encoded = new byte[POINT_BYTES]; + if (0 == encodePoint(p2, encoded, 0)) + fail("Point encoding failed"); + + System.out.printf("sum: %s%n", HashCode.fromBytes(encoded)); + assertArrayEquals(g3, encoded); + } + + @Test + public void testSimpleSign() { + byte[] privateKey = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes(); + byte[] message = HashCode.fromString("01234567".toLowerCase()).asBytes(); + + byte[] signature = signForAggregation(privateKey, message); + System.out.printf("signature: %s%n", HashCode.fromBytes(signature)); + } + + @Test + public void testSimpleVerify() { + byte[] privateKey = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes(); + byte[] message = HashCode.fromString("01234567".toLowerCase()).asBytes(); + byte[] signature = HashCode.fromString("13e58e88f3df9e06637d2d5bbb814c028e3ba135494530b9d3b120bdb31168d62c70a37ae9cfba816fe6038ee1ce2fb521b95c4a91c7ff0bb1dd2e67733f2b0d".toLowerCase()).asBytes(); + + byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + Qortal25519Extras.generatePublicKey(privateKey, 0, publicKey, 0); + + assertTrue(verifyAggregated(publicKey, signature, message)); + } + + @Test + public void testSimpleSignAndVerify() { + byte[] privateKey = HashCode.fromString("0100000000000000000000000000000000000000000000000000000000000000".toLowerCase()).asBytes(); + byte[] message = HashCode.fromString("01234567".toLowerCase()).asBytes(); + + byte[] signature = signForAggregation(privateKey, message); + + byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + Qortal25519Extras.generatePublicKey(privateKey, 0, publicKey, 0); + + assertTrue(verifyAggregated(publicKey, signature, message)); + } + + @Test + public void testSimpleAggregate() { + List onlineAccounts = AccountUtils.generateOnlineAccounts(1); + + byte[] aggregatePublicKey = aggregatePublicKeys(onlineAccounts.stream().map(OnlineAccountData::getPublicKey).collect(Collectors.toUnmodifiableList())); + System.out.printf("Aggregate public key: %s%n", HashCode.fromBytes(aggregatePublicKey)); + + byte[] aggregateSignature = aggregateSignatures(onlineAccounts.stream().map(OnlineAccountData::getSignature).collect(Collectors.toUnmodifiableList())); + System.out.printf("Aggregate signature: %s%n", HashCode.fromBytes(aggregateSignature)); + + OnlineAccountData onlineAccount = onlineAccounts.get(0); + + assertArrayEquals(String.format("expected: %s, actual: %s", HashCode.fromBytes(onlineAccount.getPublicKey()), HashCode.fromBytes(aggregatePublicKey)), onlineAccount.getPublicKey(), aggregatePublicKey); + assertArrayEquals(String.format("expected: %s, actual: %s", HashCode.fromBytes(onlineAccount.getSignature()), HashCode.fromBytes(aggregateSignature)), onlineAccount.getSignature(), aggregateSignature); + + // This is the crucial test: + long timestamp = onlineAccount.getTimestamp(); + byte[] timestampBytes = Longs.toByteArray(timestamp); + assertTrue(verifyAggregated(aggregatePublicKey, aggregateSignature, timestampBytes)); + } + + @Test + public void testMultipleAggregate() { + List onlineAccounts = AccountUtils.generateOnlineAccounts(5000); + + byte[] aggregatePublicKey = aggregatePublicKeys(onlineAccounts.stream().map(OnlineAccountData::getPublicKey).collect(Collectors.toUnmodifiableList())); + System.out.printf("Aggregate public key: %s%n", HashCode.fromBytes(aggregatePublicKey)); + + byte[] aggregateSignature = aggregateSignatures(onlineAccounts.stream().map(OnlineAccountData::getSignature).collect(Collectors.toUnmodifiableList())); + System.out.printf("Aggregate signature: %s%n", HashCode.fromBytes(aggregateSignature)); + + OnlineAccountData onlineAccount = onlineAccounts.get(0); + + // This is the crucial test: + long timestamp = onlineAccount.getTimestamp(); + byte[] timestampBytes = Longs.toByteArray(timestamp); + assertTrue(verifyAggregated(aggregatePublicKey, aggregateSignature, timestampBytes)); + } +} diff --git a/src/test/java/org/qortal/test/SelfSponsorshipAlgoV1Tests.java b/src/test/java/org/qortal/test/SelfSponsorshipAlgoV1Tests.java new file mode 100644 index 00000000..397a1bbe --- /dev/null +++ b/src/test/java/org/qortal/test/SelfSponsorshipAlgoV1Tests.java @@ -0,0 +1,1572 @@ +package org.qortal.test; + +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.Account; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.asset.Asset; +import org.qortal.block.Block; +import org.qortal.controller.BlockMinter; +import org.qortal.data.transaction.BaseTransactionData; +import org.qortal.data.transaction.PaymentTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.data.transaction.TransferPrivsTransactionData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; +import org.qortal.test.common.*; +import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.Transaction; +import org.qortal.transaction.TransferPrivsTransaction; +import org.qortal.utils.NTP; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.qortal.test.common.AccountUtils.fee; +import static org.qortal.transaction.Transaction.ValidationResult.*; + +public class SelfSponsorshipAlgoV1Tests extends Common { + + + @Before + public void beforeTest() throws DataException { + Common.useSettings("test-settings-v2-self-sponsorship-algo.json"); + NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset()); + } + + + @Test + public void testSingleSponsor() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob self sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(11, block.getBlockData().getOnlineAccountsCount()); + assertEquals(10, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that bob and his sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testMultipleSponsors() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors 10 accounts + List chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccounts.addAll(chloeSponseesOnlineAccounts); + + // Dilbert sponsors 5 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(26, block.getBlockData().getOnlineAccountsCount()); + assertEquals(25, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that chloe and her sponsees have no penalties + List chloeAndSponsees = new ArrayList<>(chloeSponsees); + chloeAndSponsees.add(chloeAccount); + for (PrivateKeyAccount chloeSponsee : chloeAndSponsees) + assertEquals(0, (int) new Account(repository, chloeSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that dilbert and his sponsees have no penalties + List dilbertAndSponsees = new ArrayList<>(dilbertSponsees); + dilbertAndSponsees.add(dilbertAccount); + for (PrivateKeyAccount dilbertSponsee : dilbertAndSponsees) + assertEquals(0, (int) new Account(repository, dilbertSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that bob and his sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that chloe and her sponsees still have no penalties + for (PrivateKeyAccount chloeSponsee : chloeAndSponsees) + assertEquals(0, (int) new Account(repository, chloeSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that dilbert and his sponsees still have no penalties + for (PrivateKeyAccount dilbertSponsee : dilbertAndSponsees) + assertEquals(0, (int) new Account(repository, dilbertSponsee.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testMintBlockWithSignerPenalty() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + List onlineAccountsAliceSigner = new ArrayList<>(); + List onlineAccountsBobSigner = new ArrayList<>(); + + // Alice self share online, and will be used to mint (some of) the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + onlineAccountsAliceSigner.add(aliceSelfShare); + + // Bob self share online, and will be used to mint (some of) the blocks + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + onlineAccountsBobSigner.add(bobSelfShare); + + // Include Alice and Bob's online accounts in each other's arrays + onlineAccountsAliceSigner.add(bobSelfShare); + onlineAccountsBobSigner.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccountsAliceSigner.addAll(bobSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors 10 accounts + List chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccountsAliceSigner.addAll(chloeSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(chloeSponseesOnlineAccounts); + + // Dilbert sponsors 5 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccountsAliceSigner.addAll(dilbertSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks (Bob is the signer) + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + + // Get reward share transaction count + assertEquals(25, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up (Bob is the signer) + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccountsAliceSigner.addAll(bobSponseeSelfShares); + onlineAccountsBobSigner.addAll(bobSponseeSelfShares); + + // Mint blocks (Bob is the signer) + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 19 (the algo runs at block 20) (Bob is the signer) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs (Bob is the signer) + // Block should be valid, because new account levels don't take effect until next block's validation + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Mint a block, but Bob is now an invalid signer because he is level 0 + block = BlockMinter.mintTestingBlockUnvalidated(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + // Block should be null as it's unable to be minted + assertNull(block); + + // Mint the same block with Alice as the signer, and this time it should be valid + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + // Block should NOT be null + assertNotNull(block); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testMintBlockWithFounderSignerPenalty() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + List onlineAccountsAliceSigner = new ArrayList<>(); + List onlineAccountsBobSigner = new ArrayList<>(); + + // Alice self share online, and will be used to mint (some of) the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + onlineAccountsAliceSigner.add(aliceSelfShare); + + // Bob self share online, and will be used to mint (some of) the blocks + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + onlineAccountsBobSigner.add(bobSelfShare); + + // Include Alice and Bob's online accounts in each other's arrays + onlineAccountsAliceSigner.add(bobSelfShare); + onlineAccountsBobSigner.add(aliceSelfShare); + + PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Alice sponsors 10 accounts + List aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10); + List aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees); + onlineAccountsAliceSigner.addAll(aliceSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(aliceSponseesOnlineAccounts); + + // Bob sponsors 9 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 9); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccountsAliceSigner.addAll(bobSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(bobSponseesOnlineAccounts); + + // Mint blocks (Bob is the signer) + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + + // Get reward share transaction count + assertEquals(19, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up (Alice is the signer) + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees); + onlineAccountsAliceSigner.addAll(aliceSponseeSelfShares); + onlineAccountsBobSigner.addAll(aliceSponseeSelfShares); + + // Mint blocks (Bob is the signer) + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Alice then consolidates funds + consolidateFunds(repository, aliceSponsees, aliceAccount); + + // Mint until block 19 (the algo runs at block 20) (Bob is the signer) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that alice and her sponsees have no penalties + List aliceAndSponsees = new ArrayList<>(aliceSponsees); + aliceAndSponsees.add(aliceAccount); + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(0, (int) new Account(repository, aliceSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs (Alice is the signer) + // Block should be valid, because new account levels don't take effect until next block's validation + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + + // Ensure that alice and her sponsees now have penalties + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(-5000000, (int) new Account(repository, aliceSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that alice and her sponsees are now level 0 + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(0, (int) new Account(repository, aliceSponsee.getAddress()).getLevel()); + + // Mint a block, but Alice is now an invalid signer because she has lost founder minting abilities + block = BlockMinter.mintTestingBlockUnvalidated(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + // Block should be null as it's unable to be minted + assertNull(block); + + // Mint the same block with Bob as the signer, and this time it should be valid + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + // Block should NOT be null + assertNotNull(block); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testOnlineAccountsWithPenalties() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + // Bob self share online + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + onlineAccounts.add(bobSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors 10 accounts + List chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccounts.addAll(chloeSponseesOnlineAccounts); + + // Dilbert sponsors 5 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(27, block.getBlockData().getOnlineAccountsCount()); + assertEquals(25, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Ensure that chloe's sponsees are present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(chloeSponsees, block)); + + // Ensure that dilbert's sponsees are present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(dilbertSponsees, block)); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Mint another few blocks + while (block.getBlockData().getHeight() < 24) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(24, (int)block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees are NOT present in block's online accounts (due to penalties) + assertFalse(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Ensure that chloe's sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(chloeSponsees, block)); + + // Ensure that dilbert's sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(dilbertSponsees, block)); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testFounderOnlineAccountsWithPenalties() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Bob self share online, and will be used to mint the blocks + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(bobSelfShare); + + // Alice self share online + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Alice sponsors 10 accounts + List aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10); + List aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees); + onlineAccounts.addAll(aliceSponseesOnlineAccounts); + onlineAccounts.addAll(aliceSponseesOnlineAccounts); + + // Bob sponsors 9 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 9); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks (Bob is the signer) + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Get reward share transaction count + assertEquals(19, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up (Alice is the signer) + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees); + onlineAccounts.addAll(aliceSponseeSelfShares); + + // Mint blocks (Bob is the signer) + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Alice then consolidates funds + consolidateFunds(repository, aliceSponsees, aliceAccount); + + // Mint until block 19 (the algo runs at block 20) (Bob is the signer) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that alice and her sponsees have no penalties + List aliceAndSponsees = new ArrayList<>(aliceSponsees); + aliceAndSponsees.add(aliceAccount); + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(0, (int) new Account(repository, aliceSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs (Alice is the signer) + // Block should be valid, because new account levels don't take effect until next block's validation + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that alice and her sponsees now have penalties + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(-5000000, (int) new Account(repository, aliceSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that alice and her sponsees are now level 0 + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(0, (int) new Account(repository, aliceSponsee.getAddress()).getLevel()); + + // Ensure that alice and her sponsees don't have penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Ensure that alice and her sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(aliceAndSponsees, block)); + + // Mint another few blocks + while (block.getBlockData().getHeight() < 24) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(24, (int)block.getBlockData().getHeight()); + + // Ensure that alice and her sponsees are NOT present in block's online accounts (due to penalties) + assertFalse(areAllAccountsPresentInBlock(aliceAndSponsees, block)); + + // Ensure that bob and his sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testPenaltyAccountCreateRewardShare() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors 10 accounts + List chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccounts.addAll(chloeSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(21, block.getBlockData().getOnlineAccountsCount()); + assertEquals(20, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Bob creates a valid reward share transaction + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount)); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Bob can no longer create a reward share transaction + assertEquals(Transaction.ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE, AccountUtils.createRandomRewardShare(repository, bobAccount)); + + // ... but Chloe still can + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, chloeAccount)); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Bob creates another valid reward share transaction + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount)); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testPenaltyFounderCreateRewardShare() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Bob self share online, and will be used to mint the blocks + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(bobSelfShare); + + PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Alice sponsors 10 accounts + List aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10); + List aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees); + onlineAccounts.addAll(aliceSponseesOnlineAccounts); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(21, block.getBlockData().getOnlineAccountsCount()); + assertEquals(20, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Generate self shares so the sponsees can start minting + List aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees); + onlineAccounts.addAll(aliceSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Alice then consolidates funds + consolidateFunds(repository, aliceSponsees, aliceAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Alice creates a valid reward share transaction + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, aliceAccount)); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that alice now has a penalty + assertEquals(-5000000, (int) new Account(repository, aliceAccount.getAddress()).getBlocksMintedPenalty()); + + // Ensure that alice and her sponsees are now level 0 + assertEquals(0, (int) new Account(repository, aliceAccount.getAddress()).getLevel()); + + // Alice can no longer create a reward share transaction + assertEquals(Transaction.ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE, AccountUtils.createRandomRewardShare(repository, aliceAccount)); + + // ... but Bob still can + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount)); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Alice creates another valid reward share transaction + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, aliceAccount)); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + /** + * This is a test to prove that Dilbert levels up from 6 to 7 in the same block that the self + * sponsorship algo runs. It is here to give some confidence in the following testPenaltyAccountLevelUp() + * test, in which we will test what happens if a penalty is applied or removed in the same block + * that an account would otherwise have leveled up. It also gives some confidence that the algo + * doesn't affect the levels of unflagged accounts. + * + * @throws DataException + */ + @Test + public void testNonPenaltyAccountLevelUp() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Dilbert sponsors 10 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 10); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Make sure Dilbert hasn't leveled up yet + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Make sure Dilbert has leveled up + assertEquals(7, (int)dilbertAccount.getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Make sure Dilbert has returned to level 6 + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testPenaltyAccountLevelUp() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Dilbert sponsors 10 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 10); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Generate self shares so the sponsees can start minting + List dilbertSponseeSelfShares = AccountUtils.generateSelfShares(repository, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Dilbert then consolidates funds + consolidateFunds(repository, dilbertSponsees, dilbertAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Make sure Dilbert hasn't leveled up yet + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Make sure Dilbert is now level 0 instead of 7 (due to penalty) + assertEquals(0, (int)dilbertAccount.getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Make sure Dilbert has returned to level 6 + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testDuplicateSponsors() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors THE SAME 10 accounts + for (PrivateKeyAccount bobSponsee : bobSponsees) { + // Create reward-share + TransactionData transactionData = AccountUtils.createRewardShare(repository, chloeAccount, bobSponsee, 0, fee); + TransactionUtils.signAndImportValid(repository, transactionData, chloeAccount); + } + List chloeSponsees = new ArrayList<>(bobSponsees); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccounts.addAll(chloeSponseesOnlineAccounts); + + // Dilbert sponsors 5 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(26, block.getBlockData().getOnlineAccountsCount()); + assertEquals(25, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that chloe and her sponsees also have penalties, as they relate to the same network of accounts + List chloeAndSponsees = new ArrayList<>(chloeSponsees); + chloeAndSponsees.add(chloeAccount); + for (PrivateKeyAccount chloeSponsee : chloeAndSponsees) + assertEquals(-5000000, (int) new Account(repository, chloeSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that dilbert and his sponsees have no penalties + List dilbertAndSponsees = new ArrayList<>(dilbertSponsees); + dilbertAndSponsees.add(dilbertAccount); + for (PrivateKeyAccount dilbertSponsee : dilbertAndSponsees) + assertEquals(0, (int) new Account(repository, dilbertSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that bob and his sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that chloe and her sponsees still have no penalties again + for (PrivateKeyAccount chloeSponsee : chloeAndSponsees) + assertEquals(0, (int) new Account(repository, chloeSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that dilbert and his sponsees still have no penalties + for (PrivateKeyAccount dilbertSponsee : dilbertAndSponsees) + assertEquals(0, (int) new Account(repository, dilbertSponsee.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testTransferPrivsBeforeAlgoBlock() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 18 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 18) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(18, (int) block.getBlockData().getHeight()); + + // Bob then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount = randomTransferPrivs(repository, bobAccount); + + // Ensure recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount.getLevel()); + + // Mint another block, so that the TRANSFER_PRIVS confirms + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the TRANSFER_PRIVS recipient has inherited Bob's level, and Bob is at level 0 + assertTrue(recipientAccount.getLevel() > 0); + assertEquals(0, (int)bobAccount.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob's sponsees are greater than level 0 + // Bob's account won't be, as he has transferred privs + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Ensure recipient account has penalty too + assertEquals(-5000000, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getLevel()); + + // TODO: check both recipients' sponsees + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that Bob's sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure recipient account has no penalty again and has a level greater than 0 + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + assertTrue(new Account(repository, recipientAccount.getAddress()).getLevel() > 0); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testTransferPrivsInAlgoBlock() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Bob then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount = randomTransferPrivs(repository, bobAccount); + + // Ensure recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the TRANSFER_PRIVS recipient has inherited Bob's level, and Bob is at level 0 + assertTrue(recipientAccount.getLevel() > 0); + assertEquals(0, (int)bobAccount.getLevel()); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure recipient has no level again + assertNull(recipientAccount.getLevel()); + + // Ensure that bob and his sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testTransferPrivsAfterAlgoBlock() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 19 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 19) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(19, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Bob then issues a TRANSFER_PRIVS, which should be invalid + Transaction transferPrivsTransaction = randomTransferPrivsTransaction(repository, bobAccount); + assertEquals(ACCOUNT_NOT_TRANSFERABLE, transferPrivsTransaction.isValid()); + + // Orphan last 2 blocks + BlockUtils.orphanLastBlock(repository); + BlockUtils.orphanLastBlock(repository); + + // TRANSFER_PRIVS should now be valid + transferPrivsTransaction = randomTransferPrivsTransaction(repository, bobAccount); + assertEquals(OK, transferPrivsTransaction.isValid()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testDoubleTransferPrivs() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 17 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 17) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(17, (int) block.getBlockData().getHeight()); + + // Bob then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount1 = randomTransferPrivs(repository, bobAccount); + + // Ensure recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount1.getLevel()); + + // Bob and also sends some QORT to cover future transaction fees + // This mints another block, and the TRANSFER_PRIVS confirms + AccountUtils.pay(repository, bobAccount, recipientAccount1.getAddress(), 123456789L); + + // Now ensure that the TRANSFER_PRIVS recipient has inherited Bob's level, and Bob is at level 0 + assertTrue(recipientAccount1.getLevel() > 0); + assertEquals(0, (int)bobAccount.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + + // The recipient account then issues a TRANSFER_PRIVS of their own + PrivateKeyAccount recipientAccount2 = randomTransferPrivs(repository, recipientAccount1); + + // Ensure recipientAccount2 has no level at this point (pre-confirmation) + assertNull(recipientAccount2.getLevel()); + + // Mint another block, so that the TRANSFER_PRIVS confirms + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the TRANSFER_PRIVS recipient2 has inherited Bob's level, and recipient1 is at level 0 + assertTrue(recipientAccount2.getLevel() > 0); + assertEquals(0, (int)recipientAccount1.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob's sponsees are greater than level 0 + // Bob's account won't be, as he has transferred privs + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Ensure recipientAccount2 has penalty too + assertEquals(-5000000, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getLevel()); + + // Ensure recipientAccount1 has penalty too + assertEquals(-5000000, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getLevel()); + + // TODO: check recipient's sponsees + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that Bob's sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure recipientAccount1 has no penalty again and is level 0 + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getLevel()); + + // Ensure recipientAccount2 has no penalty again and has a level greater than 0 + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + assertTrue(new Account(repository, recipientAccount2.getAddress()).getLevel() > 0); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + + + private static PrivateKeyAccount randomTransferPrivs(Repository repository, PrivateKeyAccount senderAccount) throws DataException { + // Generate random recipient account + byte[] randomPrivateKey = new byte[32]; + new Random().nextBytes(randomPrivateKey); + PrivateKeyAccount recipientAccount = new PrivateKeyAccount(repository, randomPrivateKey); + + BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), 0, senderAccount.getLastReference(), senderAccount.getPublicKey(), fee, null); + TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, recipientAccount.getAddress()); + + TransactionUtils.signAndImportValid(repository, transactionData, senderAccount); + + return recipientAccount; + } + + private static TransferPrivsTransaction randomTransferPrivsTransaction(Repository repository, PrivateKeyAccount senderAccount) throws DataException { + // Generate random recipient account + byte[] randomPrivateKey = new byte[32]; + new Random().nextBytes(randomPrivateKey); + PrivateKeyAccount recipientAccount = new PrivateKeyAccount(repository, randomPrivateKey); + + BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), 0, senderAccount.getLastReference(), senderAccount.getPublicKey(), fee, null); + TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, recipientAccount.getAddress()); + + return new TransferPrivsTransaction(repository, transactionData); + } + + private boolean areAllAccountsPresentInBlock(List accounts, Block block) throws DataException { + for (PrivateKeyAccount bobSponsee : accounts) { + boolean foundOnlineAccountInBlock = false; + for (Block.ExpandedAccount expandedAccount : block.getExpandedAccounts()) { + if (expandedAccount.getRecipientAccount().getAddress().equals(bobSponsee.getAddress())) { + foundOnlineAccountInBlock = true; + break; + } + } + if (!foundOnlineAccountInBlock) { + return false; + } + } + return true; + } + + private static void consolidateFunds(Repository repository, List sponsees, PrivateKeyAccount sponsor) throws DataException { + for (PrivateKeyAccount sponsee : sponsees) { + for (int i = 0; i < 5; i++) { + // Generate new payments from sponsee to sponsor + TransactionData paymentData = new PaymentTransactionData(TestTransaction.generateBase(sponsee), sponsor.getAddress(), 1); + TransactionUtils.signAndImportValid(repository, paymentData, sponsee); // updates paymentData's signature + } + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/qortal/test/api/BlockApiTests.java b/src/test/java/org/qortal/test/api/BlockApiTests.java index 47d5318a..23e7b007 100644 --- a/src/test/java/org/qortal/test/api/BlockApiTests.java +++ b/src/test/java/org/qortal/test/api/BlockApiTests.java @@ -84,7 +84,7 @@ public class BlockApiTests extends ApiCommon { @Test public void testGetBlockRange() { - assertNotNull(this.blocksResource.getBlockRange(1, 1)); + assertNotNull(this.blocksResource.getBlockRange(1, 1, false, false)); List testValues = Arrays.asList(null, Integer.valueOf(1)); diff --git a/src/test/java/org/qortal/test/apps/RewardShareKeys.java b/src/test/java/org/qortal/test/apps/RewardShareKeys.java index e0bfc1cf..5ba1aab4 100644 --- a/src/test/java/org/qortal/test/apps/RewardShareKeys.java +++ b/src/test/java/org/qortal/test/apps/RewardShareKeys.java @@ -6,6 +6,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PublicKeyAccount; +import org.qortal.crypto.Crypto; import org.qortal.utils.Base58; public class RewardShareKeys { @@ -28,7 +29,7 @@ public class RewardShareKeys { PublicKeyAccount recipientAccount = new PublicKeyAccount(null, args.length > 1 ? Base58.decode(args[1]) : minterAccount.getPublicKey()); byte[] rewardSharePrivateKey = minterAccount.getRewardSharePrivateKey(recipientAccount.getPublicKey()); - byte[] rewardSharePublicKey = PrivateKeyAccount.toPublicKey(rewardSharePrivateKey); + byte[] rewardSharePublicKey = Crypto.toPublicKey(rewardSharePrivateKey); System.out.println(String.format("Minter account: %s", minterAccount.getAddress())); System.out.println(String.format("Minter's public key: %s", Base58.encode(minterAccount.getPublicKey()))); diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java index 4db8bdc7..96843876 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java @@ -1,11 +1,26 @@ package org.qortal.test.arbitrary; +import org.apache.commons.lang3.reflect.FieldUtils; import org.junit.Before; import org.junit.Test; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.arbitrary.ArbitraryDataFile; +import org.qortal.arbitrary.ArbitraryDataReader; +import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.misc.Service; import org.qortal.arbitrary.misc.Service.ValidationResult; +import org.qortal.controller.arbitrary.ArbitraryDataManager; +import org.qortal.data.transaction.ArbitraryTransactionData; +import org.qortal.data.transaction.RegisterNameTransactionData; import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.ArbitraryUtils; import org.qortal.test.common.Common; +import org.qortal.test.common.TransactionUtils; +import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.RegisterNameTransaction; +import org.qortal.utils.Base58; import java.io.IOException; import java.nio.file.Files; @@ -102,77 +117,276 @@ public class ArbitraryServiceTests extends Common { } @Test - public void testValidQortalMetadata() throws IOException { - // Metadata is to describe an arbitrary resource (title, description, tags, etc) - String dataString = "{\"title\":\"Test Title\", \"description\":\"Test description\", \"tags\":[\"test\"]}"; + public void testValidateGifRepository() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); - // Write to temp path - Path path = Files.createTempFile("testValidQortalMetadata", null); + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateGifRepository"); path.toFile().deleteOnExit(); - Files.write(path, dataString.getBytes(), StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image1.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image2.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image3.gif"), data, StandardOpenOption.CREATE); - Service service = Service.QORTAL_METADATA; + Service service = Service.GIF_REPOSITORY; assertTrue(service.isValidationRequired()); + assertEquals(ValidationResult.OK, service.validate(path)); } @Test - public void testQortalMetadataMissingKeys() throws IOException { - // Metadata is to describe an arbitrary resource (title, description, tags, etc) - String dataString = "{\"description\":\"Test description\", \"tags\":[\"test\"]}"; + public void testValidateSingleFileGifRepository() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); - // Write to temp path - Path path = Files.createTempFile("testQortalMetadataMissingKeys", null); + // Write the data to a single file in a temp path + Path path = Files.createTempDirectory("testValidateSingleFileGifRepository"); path.toFile().deleteOnExit(); - Files.write(path, dataString.getBytes(), StandardOpenOption.CREATE); + Path imagePath = Paths.get(path.toString(), "image1.gif"); + Files.write(imagePath, data, StandardOpenOption.CREATE); - Service service = Service.QORTAL_METADATA; + Service service = Service.GIF_REPOSITORY; assertTrue(service.isValidationRequired()); - assertEquals(ValidationResult.MISSING_KEYS, service.validate(path)); + + assertEquals(ValidationResult.OK, service.validate(imagePath)); } @Test - public void testQortalMetadataTooLarge() throws IOException { - // Metadata is to describe an arbitrary resource (title, description, tags, etc) - String dataString = "{\"title\":\"Test Title\", \"description\":\"Test description\", \"tags\":[\"test\"]}"; + public void testValidateMultiLayerGifRepository() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); - // Generate some large data to go along with it - int largeDataSize = 11*1024; // Larger than allowed 10kiB - byte[] largeData = new byte[largeDataSize]; - new Random().nextBytes(largeData); - - // Write to temp path - Path path = Files.createTempDirectory("testQortalMetadataTooLarge"); + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateMultiLayerGifRepository"); path.toFile().deleteOnExit(); - Files.write(Paths.get(path.toString(), "data"), dataString.getBytes(), StandardOpenOption.CREATE); - Files.write(Paths.get(path.toString(), "large_data"), largeData, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image1.gif"), data, StandardOpenOption.CREATE); - Service service = Service.QORTAL_METADATA; + Path subdirectory = Paths.get(path.toString(), "subdirectory"); + Files.createDirectories(subdirectory); + Files.write(Paths.get(subdirectory.toString(), "image2.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(subdirectory.toString(), "image3.gif"), data, StandardOpenOption.CREATE); + + Service service = Service.GIF_REPOSITORY; assertTrue(service.isValidationRequired()); - assertEquals(ValidationResult.EXCEEDS_SIZE_LIMIT, service.validate(path)); + + assertEquals(ValidationResult.DIRECTORIES_NOT_ALLOWED, service.validate(path)); } @Test - public void testMultipleFileMetadata() throws IOException { - // Metadata is to describe an arbitrary resource (title, description, tags, etc) - String dataString = "{\"title\":\"Test Title\", \"description\":\"Test description\", \"tags\":[\"test\"]}"; + public void testValidateEmptyGifRepository() throws IOException { + Path path = Files.createTempDirectory("testValidateEmptyGifRepository"); - // Generate some large data to go along with it - int otherDataSize = 1024; // Smaller than 10kiB limit - byte[] otherData = new byte[otherDataSize]; - new Random().nextBytes(otherData); - - // Write to temp path - Path path = Files.createTempDirectory("testMultipleFileMetadata"); - path.toFile().deleteOnExit(); - Files.write(Paths.get(path.toString(), "data"), dataString.getBytes(), StandardOpenOption.CREATE); - Files.write(Paths.get(path.toString(), "other_data"), otherData, StandardOpenOption.CREATE); - - Service service = Service.QORTAL_METADATA; + Service service = Service.GIF_REPOSITORY; assertTrue(service.isValidationRequired()); - // There are multiple files, so we don't know which one to parse as JSON - assertEquals(ValidationResult.MISSING_KEYS, service.validate(path)); + assertEquals(ValidationResult.MISSING_DATA, service.validate(path)); } -} + @Test + public void testValidateInvalidGifRepository() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateInvalidGifRepository"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "image1.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image2.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image3.jpg"), data, StandardOpenOption.CREATE); // Invalid extension + + Service service = Service.GIF_REPOSITORY; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.INVALID_FILE_EXTENSION, service.validate(path)); + } + + @Test + public void testValidatePublishedGifRepository() throws IOException, DataException, MissingDataException, IllegalAccessException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateGifRepository"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "image1.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image2.gif"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "image3.gif"), data, StandardOpenOption.CREATE); + + Service service = Service.GIF_REPOSITORY; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.OK, service.validate(path)); + + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String publicKey58 = Base58.encode(alice.getPublicKey()); + String name = "TEST"; // Can be anything for this test + String identifier = "test_identifier"; + + // Register the name to Alice + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); + transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + TransactionUtils.signAndMint(repository, transactionData, alice); + + // Set difficulty to 1 + FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true); + + // Create PUT transaction + ArbitraryUtils.createAndMintTxn(repository, publicKey58, path, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice); + + // Build the latest data state for this name, and no exceptions should be thrown because validation passes + ArbitraryDataReader arbitraryDataReader1a = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, identifier); + arbitraryDataReader1a.loadSynchronously(true); + } + } + + @Test + public void testValidateQChatAttachment() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data a single file in a temp path + Path path = Files.createTempDirectory("testValidateQChatAttachment"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "document.pdf"), data, StandardOpenOption.CREATE); + + Service service = Service.QCHAT_ATTACHMENT; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.OK, service.validate(path)); + } + + @Test + public void testValidateSingleFileQChatAttachment() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data a single file in a temp path + Path path = Files.createTempDirectory("testValidateSingleFileQChatAttachment"); + path.toFile().deleteOnExit(); + Path filePath = Paths.get(path.toString(), "document.pdf"); + Files.write(filePath, data, StandardOpenOption.CREATE); + + Service service = Service.QCHAT_ATTACHMENT; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.OK, service.validate(filePath)); + } + + @Test + public void testValidateInvalidQChatAttachmentFileExtension() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data a single file in a temp path + Path path = Files.createTempDirectory("testValidateInvalidQChatAttachmentFileExtension"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "application.exe"), data, StandardOpenOption.CREATE); + + Service service = Service.QCHAT_ATTACHMENT; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.INVALID_FILE_EXTENSION, service.validate(path)); + } + + @Test + public void testValidateEmptyQChatAttachment() throws IOException { + Path path = Files.createTempDirectory("testValidateEmptyQChatAttachment"); + + Service service = Service.QCHAT_ATTACHMENT; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.INVALID_FILE_COUNT, service.validate(path)); + } + + @Test + public void testValidateMultiLayerQChatAttachment() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateMultiLayerQChatAttachment"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "file1.txt"), data, StandardOpenOption.CREATE); + + Path subdirectory = Paths.get(path.toString(), "subdirectory"); + Files.createDirectories(subdirectory); + Files.write(Paths.get(subdirectory.toString(), "file2.txt"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(subdirectory.toString(), "file3.txt"), data, StandardOpenOption.CREATE); + + Service service = Service.QCHAT_ATTACHMENT; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.DIRECTORIES_NOT_ALLOWED, service.validate(path)); + } + + @Test + public void testValidateMultiFileQChatAttachment() throws IOException { + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data to several files in a temp path + Path path = Files.createTempDirectory("testValidateMultiFileQChatAttachment"); + path.toFile().deleteOnExit(); + Files.write(Paths.get(path.toString(), "file1.txt"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(path.toString(), "file2.txt"), data, StandardOpenOption.CREATE); + + Service service = Service.QCHAT_ATTACHMENT; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.INVALID_FILE_COUNT, service.validate(path)); + } + + @Test + public void testValidatePublishedQChatAttachment() throws IOException, DataException, MissingDataException, IllegalAccessException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Generate some random data + byte[] data = new byte[1024]; + new Random().nextBytes(data); + + // Write the data a single file in a temp path + Path path = Files.createTempDirectory("testValidateSingleFileQChatAttachment"); + path.toFile().deleteOnExit(); + Path filePath = Paths.get(path.toString(), "document.pdf"); + Files.write(filePath, data, StandardOpenOption.CREATE); + + Service service = Service.QCHAT_ATTACHMENT; + assertTrue(service.isValidationRequired()); + + assertEquals(ValidationResult.OK, service.validate(filePath)); + + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String publicKey58 = Base58.encode(alice.getPublicKey()); + String name = "TEST"; // Can be anything for this test + String identifier = "test_identifier"; + + // Register the name to Alice + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); + transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + TransactionUtils.signAndMint(repository, transactionData, alice); + + // Set difficulty to 1 + FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true); + + // Create PUT transaction + ArbitraryUtils.createAndMintTxn(repository, publicKey58, filePath, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice); + + // Build the latest data state for this name, and no exceptions should be thrown because validation passes + ArbitraryDataReader arbitraryDataReader1a = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, identifier); + arbitraryDataReader1a.loadSynchronously(true); + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java index 357046fe..5d28568d 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java @@ -12,6 +12,7 @@ import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Service; import org.qortal.controller.arbitrary.ArbitraryDataManager; +import org.qortal.data.arbitrary.ArbitraryResourceMetadata; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.RegisterNameTransactionData; import org.qortal.repository.DataException; @@ -25,9 +26,13 @@ import org.qortal.transaction.RegisterNameTransaction; import org.qortal.utils.Base58; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.List; +import java.util.Random; import static org.junit.Assert.*; @@ -279,6 +284,94 @@ public class ArbitraryTransactionMetadataTests extends Common { } } + @Test + public void testSingleFileList() throws DataException, IOException, MissingDataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String publicKey58 = Base58.encode(alice.getPublicKey()); + String name = "TEST"; // Can be anything for this test + String identifier = null; // Not used for this test + Service service = Service.ARBITRARY_DATA; + int chunkSize = 100; + int dataLength = 900; // Actual data length will be longer due to encryption + + // Register the name to Alice + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); + transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + TransactionUtils.signAndMint(repository, transactionData, alice); + + // Add a few files at multiple levels + byte[] data = new byte[1024]; + new Random().nextBytes(data); + Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); + Path file1 = Paths.get(path1.toString(), "file.txt"); + + // Create PUT transaction + ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, file1, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize); + + // Check the file list metadata is correct + assertEquals(1, arbitraryDataFile.getMetadata().getFiles().size()); + assertTrue(arbitraryDataFile.getMetadata().getFiles().contains("file.txt")); + + // Ensure the file list can be read back out again, when specified to be included + ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(arbitraryDataFile.getMetadata(), true); + assertTrue(resourceMetadata.getFiles().contains("file.txt")); + + // Ensure it's not returned when specified to be excluded + // The entire object will be null because there is no metadata + ArbitraryResourceMetadata resourceMetadataSimple = ArbitraryResourceMetadata.fromTransactionMetadata(arbitraryDataFile.getMetadata(), false); + assertNull(resourceMetadataSimple); + } + } + + @Test + public void testMultipleFileList() throws DataException, IOException, MissingDataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String publicKey58 = Base58.encode(alice.getPublicKey()); + String name = "TEST"; // Can be anything for this test + String identifier = null; // Not used for this test + Service service = Service.ARBITRARY_DATA; + int chunkSize = 100; + int dataLength = 900; // Actual data length will be longer due to encryption + + // Register the name to Alice + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); + transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + TransactionUtils.signAndMint(repository, transactionData, alice); + + // Add a few files at multiple levels + byte[] data = new byte[1024]; + new Random().nextBytes(data); + Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); + Files.write(Paths.get(path1.toString(), "image1.jpg"), data, StandardOpenOption.CREATE); + + Path subdirectory = Paths.get(path1.toString(), "subdirectory"); + Files.createDirectories(subdirectory); + Files.write(Paths.get(subdirectory.toString(), "config.json"), data, StandardOpenOption.CREATE); + + // Create PUT transaction + ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize); + + // Check the file list metadata is correct + assertEquals(3, arbitraryDataFile.getMetadata().getFiles().size()); + assertTrue(arbitraryDataFile.getMetadata().getFiles().contains("file.txt")); + assertTrue(arbitraryDataFile.getMetadata().getFiles().contains("image1.jpg")); + assertTrue(arbitraryDataFile.getMetadata().getFiles().contains("subdirectory/config.json")); + + // Ensure the file list can be read back out again, when specified to be included + ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(arbitraryDataFile.getMetadata(), true); + assertTrue(resourceMetadata.getFiles().contains("file.txt")); + assertTrue(resourceMetadata.getFiles().contains("image1.jpg")); + assertTrue(resourceMetadata.getFiles().contains("subdirectory/config.json")); + + // Ensure it's not returned when specified to be excluded + // The entire object will be null because there is no metadata + ArbitraryResourceMetadata resourceMetadataSimple = ArbitraryResourceMetadata.fromTransactionMetadata(arbitraryDataFile.getMetadata(), false); + assertNull(resourceMetadataSimple); + } + } + @Test public void testExistingCategories() { // Matching categories should be correctly located diff --git a/src/test/java/org/qortal/test/common/AccountUtils.java b/src/test/java/org/qortal/test/common/AccountUtils.java index 0e7ef020..bdfd124b 100644 --- a/src/test/java/org/qortal/test/common/AccountUtils.java +++ b/src/test/java/org/qortal/test/common/AccountUtils.java @@ -1,11 +1,16 @@ package org.qortal.test.common; import static org.junit.Assert.assertEquals; +import static org.qortal.crypto.Qortal25519Extras.signForAggregation; -import java.util.HashMap; -import java.util.Map; +import java.security.SecureRandom; +import java.util.*; +import com.google.common.primitives.Longs; import org.qortal.account.PrivateKeyAccount; +import org.qortal.crypto.Crypto; +import org.qortal.crypto.Qortal25519Extras; +import org.qortal.data.network.OnlineAccountData; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.PaymentTransactionData; import org.qortal.data.transaction.RewardShareTransactionData; @@ -13,6 +18,8 @@ import org.qortal.data.transaction.TransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.transaction.Transaction; +import org.qortal.transform.Transformer; import org.qortal.utils.Amounts; public class AccountUtils { @@ -20,6 +27,8 @@ public class AccountUtils { public static final int txGroupId = Group.NO_GROUP; public static final long fee = 1L * Amounts.MULTIPLIER; + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + public static void pay(Repository repository, String testSenderName, String testRecipientName, long amount) throws DataException { PrivateKeyAccount sendingAccount = Common.getTestAccount(repository, testSenderName); PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, testRecipientName); @@ -40,12 +49,15 @@ public class AccountUtils { public static TransactionData createRewardShare(Repository repository, String minter, String recipient, int sharePercent) throws DataException { PrivateKeyAccount mintingAccount = Common.getTestAccount(repository, minter); PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient); + return createRewardShare(repository, mintingAccount, recipientAccount, sharePercent, fee); + } + public static TransactionData createRewardShare(Repository repository, PrivateKeyAccount mintingAccount, PrivateKeyAccount recipientAccount, int sharePercent, long fee) throws DataException { byte[] reference = mintingAccount.getLastReference(); long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1; byte[] rewardSharePrivateKey = mintingAccount.getRewardSharePrivateKey(recipientAccount.getPublicKey()); - byte[] rewardSharePublicKey = PrivateKeyAccount.toPublicKey(rewardSharePrivateKey); + byte[] rewardSharePublicKey = Crypto.toPublicKey(rewardSharePrivateKey); BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, mintingAccount.getPublicKey(), fee, null); TransactionData transactionData = new RewardShareTransactionData(baseTransactionData, recipientAccount.getAddress(), rewardSharePublicKey, sharePercent); @@ -65,6 +77,70 @@ public class AccountUtils { return rewardSharePrivateKey; } + public static byte[] rewardShare(Repository repository, PrivateKeyAccount minterAccount, PrivateKeyAccount recipientAccount, int sharePercent) throws DataException { + TransactionData transactionData = createRewardShare(repository, minterAccount, recipientAccount, sharePercent, fee); + + TransactionUtils.signAndMint(repository, transactionData, minterAccount); + byte[] rewardSharePrivateKey = minterAccount.getRewardSharePrivateKey(recipientAccount.getPublicKey()); + + return rewardSharePrivateKey; + } + + public static List generateSponsorshipRewardShares(Repository repository, PrivateKeyAccount sponsorAccount, int accountsCount) throws DataException { + final int sharePercent = 0; + Random random = new Random(); + + List sponsees = new ArrayList<>(); + for (int i = 0; i < accountsCount; i++) { + + // Generate random sponsee account + byte[] randomPrivateKey = new byte[32]; + random.nextBytes(randomPrivateKey); + PrivateKeyAccount sponseeAccount = new PrivateKeyAccount(repository, randomPrivateKey); + sponsees.add(sponseeAccount); + + // Create reward-share + TransactionData transactionData = AccountUtils.createRewardShare(repository, sponsorAccount, sponseeAccount, sharePercent, fee); + TransactionUtils.signAndImportValid(repository, transactionData, sponsorAccount); + } + + return sponsees; + } + + public static Transaction.ValidationResult createRandomRewardShare(Repository repository, PrivateKeyAccount account) throws DataException { + // Bob attempts to create a reward share transaction + byte[] randomPrivateKey = new byte[32]; + new Random().nextBytes(randomPrivateKey); + PrivateKeyAccount sponseeAccount = new PrivateKeyAccount(repository, randomPrivateKey); + TransactionData transactionData = createRewardShare(repository, account, sponseeAccount, 0, fee); + return TransactionUtils.signAndImport(repository, transactionData, account); + } + + public static List generateSelfShares(Repository repository, List accounts) throws DataException { + final int sharePercent = 0; + + for (PrivateKeyAccount account : accounts) { + // Create reward-share + TransactionData transactionData = createRewardShare(repository, account, account, sharePercent, 0L); + TransactionUtils.signAndImportValid(repository, transactionData, account); + } + + return toRewardShares(repository, null, accounts); + } + + public static List toRewardShares(Repository repository, PrivateKeyAccount parentAccount, List accounts) { + List rewardShares = new ArrayList<>(); + + for (PrivateKeyAccount account : accounts) { + PrivateKeyAccount sponsor = (parentAccount != null) ? parentAccount : account; + byte[] rewardSharePrivateKey = sponsor.getRewardSharePrivateKey(account.getPublicKey()); + PrivateKeyAccount rewardShareAccount = new PrivateKeyAccount(repository, rewardSharePrivateKey); + rewardShares.add(rewardShareAccount); + } + + return rewardShares; + } + public static Map> getBalances(Repository repository, long... assetIds) throws DataException { Map> balances = new HashMap<>(); @@ -96,4 +172,28 @@ public class AccountUtils { assertEquals(String.format("%s's %s [%d] balance incorrect", accountName, assetName, assetId), expectedBalance, actualBalance); } + + public static List generateOnlineAccounts(int numAccounts) { + List onlineAccounts = new ArrayList<>(); + + long timestamp = System.currentTimeMillis(); + byte[] timestampBytes = Longs.toByteArray(timestamp); + + for (int a = 0; a < numAccounts; ++a) { + byte[] privateKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + SECURE_RANDOM.nextBytes(privateKey); + + byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + Qortal25519Extras.generatePublicKey(privateKey, 0, publicKey, 0); + + byte[] signature = signForAggregation(privateKey, timestampBytes); + + Integer nonce = new Random().nextInt(500000); + + onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey, nonce)); + } + + return onlineAccounts; + } + } diff --git a/src/test/java/org/qortal/test/common/Common.java b/src/test/java/org/qortal/test/common/Common.java index cb782343..bb6cc1cb 100644 --- a/src/test/java/org/qortal/test/common/Common.java +++ b/src/test/java/org/qortal/test/common/Common.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.SecureRandom; import java.security.Security; import java.util.ArrayList; import java.util.Collections; @@ -25,6 +26,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.qortal.account.PrivateKeyAccount; import org.qortal.block.BlockChain; import org.qortal.data.account.AccountBalanceData; import org.qortal.data.asset.AssetData; @@ -111,8 +113,16 @@ public class Common { return testAccountsByName.values().stream().map(account -> new TestAccount(repository, account)).collect(Collectors.toList()); } + public static PrivateKeyAccount generateRandomSeedAccount(Repository repository) { + byte[] seed = new byte[32]; + new SecureRandom().nextBytes(seed); + return new PrivateKeyAccount(repository, seed); + } + public static void useSettingsAndDb(String settingsFilename, boolean dbInMemory) throws DataException { - closeRepository(); + if (RepositoryManager.getRepositoryFactory() != null) { + closeRepository(); + } // Load/check settings, which potentially sets up blockchain config, etc. LOGGER.debug(String.format("Using setting file: %s", settingsFilename)); diff --git a/src/test/java/org/qortal/test/common/transaction/ChatTestTransaction.java b/src/test/java/org/qortal/test/common/transaction/ChatTestTransaction.java new file mode 100644 index 00000000..bab1f1a0 --- /dev/null +++ b/src/test/java/org/qortal/test/common/transaction/ChatTestTransaction.java @@ -0,0 +1,40 @@ +package org.qortal.test.common.transaction; + +import org.qortal.account.PrivateKeyAccount; +import org.qortal.crypto.Crypto; +import org.qortal.data.transaction.ChatTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; + +import java.util.Random; + +public class ChatTestTransaction extends TestTransaction { + + public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException { + Random random = new Random(); + byte[] orderId = new byte[64]; + random.nextBytes(orderId); + + String sender = Crypto.toAddress(account.getPublicKey()); + int nonce = 1234567; + + // Generate random recipient + byte[] randomPrivateKey = new byte[32]; + random.nextBytes(randomPrivateKey); + PrivateKeyAccount recipientAccount = new PrivateKeyAccount(repository, randomPrivateKey); + String recipient = Crypto.toAddress(recipientAccount.getPublicKey()); + + byte[] chatReference = new byte[64]; + random.nextBytes(chatReference); + + byte[] data = new byte[4000]; + random.nextBytes(data); + + boolean isText = true; + boolean isEncrypted = true; + + return new ChatTransactionData(generateBase(account), sender, nonce, recipient, chatReference, data, isText, isEncrypted); + } + +} diff --git a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java index af879e08..07a01ce2 100644 --- a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java @@ -81,7 +81,7 @@ public class BitcoinTests extends Common { } @Test - public void testGetWalletBalance() { + public void testGetWalletBalance() throws ForeignBlockchainException { String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; Long balance = bitcoin.getWalletBalance(xprv58); diff --git a/src/test/java/org/qortal/test/crosschain/DigibyteTests.java b/src/test/java/org/qortal/test/crosschain/DigibyteTests.java index 1810d7ed..dbe81c82 100644 --- a/src/test/java/org/qortal/test/crosschain/DigibyteTests.java +++ b/src/test/java/org/qortal/test/crosschain/DigibyteTests.java @@ -81,7 +81,7 @@ public class DigibyteTests extends Common { } @Test - public void testGetWalletBalance() { + public void testGetWalletBalance() throws ForeignBlockchainException { String xprv58 = "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R"; Long balance = digibyte.getWalletBalance(xprv58); diff --git a/src/test/java/org/qortal/test/crosschain/DogecoinTests.java b/src/test/java/org/qortal/test/crosschain/DogecoinTests.java index 2b0410c3..6c070d09 100644 --- a/src/test/java/org/qortal/test/crosschain/DogecoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/DogecoinTests.java @@ -81,7 +81,7 @@ public class DogecoinTests extends Common { } @Test - public void testGetWalletBalance() { + public void testGetWalletBalance() throws ForeignBlockchainException { String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru"; Long balance = dogecoin.getWalletBalance(xprv58); diff --git a/src/test/java/org/qortal/test/crosschain/LitecoinTests.java b/src/test/java/org/qortal/test/crosschain/LitecoinTests.java index 64837347..6236483a 100644 --- a/src/test/java/org/qortal/test/crosschain/LitecoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/LitecoinTests.java @@ -80,7 +80,7 @@ public class LitecoinTests extends Common { } @Test - public void testGetWalletBalance() { + public void testGetWalletBalance() throws ForeignBlockchainException { String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; Long balance = litecoin.getWalletBalance(xprv58); diff --git a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java new file mode 100644 index 00000000..9502e45a --- /dev/null +++ b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java @@ -0,0 +1,297 @@ +package org.qortal.test.crosschain; + +import cash.z.wallet.sdk.rpc.CompactFormats.*; +import com.google.common.hash.HashCode; +import com.google.common.primitives.Bytes; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.store.BlockStoreException; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.qortal.controller.tradebot.TradeBot; +import org.qortal.crosschain.*; +import org.qortal.crypto.Crypto; +import org.qortal.repository.DataException; +import org.qortal.test.common.Common; +import org.qortal.transform.TransformationException; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; +import static org.qortal.crosschain.BitcoinyHTLC.Status.*; + +public class PirateChainTests extends Common { + + private PirateChain pirateChain; + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + pirateChain = PirateChain.getInstance(); + } + + @After + public void afterTest() { + Litecoin.resetForTesting(); + pirateChain = null; + } + + @Test + public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException { + long before = System.currentTimeMillis(); + System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime())); + long afterFirst = System.currentTimeMillis(); + + System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime())); + long afterSecond = System.currentTimeMillis(); + + long firstPeriod = afterFirst - before; + long secondPeriod = afterSecond - afterFirst; + + System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod)); + + assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L); + assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); + } + + @Test + public void testGetCompactBlocks() throws ForeignBlockchainException { + int startHeight = 1000000; + int count = 20; + + long before = System.currentTimeMillis(); + List compactBlocks = pirateChain.getCompactBlocks(startHeight, count); + long after = System.currentTimeMillis(); + + System.out.println(String.format("Retrieval took: %d ms", after-before)); + + for (CompactBlock block : compactBlocks) { + System.out.println(String.format("Block height: %d, transaction count: %d", block.getHeight(), block.getVtxCount())); + } + + assertEquals(count, compactBlocks.size()); + } + + @Test + public void testGetRawTransaction() throws ForeignBlockchainException { + String txHashLE = "fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0"; + byte[] txBytes = HashCode.fromString(txHashLE).asBytes(); + // Pirate protocol expects txids in big-endian form, but block explorers use txids in little-endian form + Bytes.reverse(txBytes); + String txHashBE = HashCode.fromBytes(txBytes).toString(); + + byte[] rawTransaction = pirateChain.getBlockchainProvider().getRawTransaction(txHashBE); + assertNotNull(rawTransaction); + } + + @Test + public void testDeriveP2SHAddressWithT3Prefix() { + byte[] creatorTradePrivateKey = TradeBot.generateTradePrivateKey(); + byte[] creatorTradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(creatorTradePrivateKey); + byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); + byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); + byte[] secretA = TradeBot.generateSecret(); + byte[] hashOfSecretA = Crypto.hash160(secretA); + int lockTime = 1653233550; + + byte[] redeemScriptBytes = PirateChainHTLC.buildScript(tradeForeignPublicKey, lockTime, creatorTradeForeignPublicKey, hashOfSecretA); + String p2shAddress = PirateChain.getInstance().deriveP2shAddress(redeemScriptBytes); + assertTrue(p2shAddress.startsWith("t3")); + } + + @Test + public void testDeriveP2SHAddressWithBPrefix() { + byte[] creatorTradePrivateKey = TradeBot.generateTradePrivateKey(); + byte[] creatorTradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(creatorTradePrivateKey); + byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); + byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); + byte[] secretA = TradeBot.generateSecret(); + byte[] hashOfSecretA = Crypto.hash160(secretA); + int lockTime = 1653233550; + + byte[] redeemScriptBytes = PirateChainHTLC.buildScript(tradeForeignPublicKey, lockTime, creatorTradeForeignPublicKey, hashOfSecretA); + String p2shAddress = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptBytes); + assertTrue(p2shAddress.startsWith("b")); + } + + @Test + public void testHTLCStatusFunded() throws ForeignBlockchainException { + String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt"; + long p2shFee = 10000; + final long minimumAmount = 10000 + p2shFee; + BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount); + assertEquals(FUNDED, htlcStatus); + } + + @Test + public void testHTLCStatusRedeemed() throws ForeignBlockchainException { + String p2shAddress = "bYZrzSSgGp8aEGvihukoMGU8sXYrx19Wka"; + long p2shFee = 10000; + final long minimumAmount = 10000 + p2shFee; + BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount); + assertEquals(REDEEMED, htlcStatus); + } + + @Test + public void testHTLCStatusRefunded() throws ForeignBlockchainException { + String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; + long p2shFee = 10000; + final long minimumAmount = 10000 + p2shFee; + BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount); + assertEquals(REFUNDED, htlcStatus); + } + + @Test + public void testGetTxidForUnspentAddress() throws ForeignBlockchainException { + String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt"; + String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress); + + // Reverse the byte order of the txid used by block explorers, to get to big-endian form + byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes(); + Bytes.reverse(expectedTxidLE); + String expectedTxidBE = HashCode.fromBytes(expectedTxidLE).toString(); + + assertEquals(expectedTxidBE, txid); + } + + @Test + public void testGetTxidForUnspentAddressWithMinimumAmount() throws ForeignBlockchainException { + String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt"; + long p2shFee = 10000; + final long minimumAmount = 10000 + p2shFee; + String txid = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount); + + // Reverse the byte order of the txid used by block explorers, to get to big-endian form + byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes(); + Bytes.reverse(expectedTxidLE); + String expectedTxidBE = HashCode.fromBytes(expectedTxidLE).toString(); + + assertEquals(expectedTxidBE, txid); + } + + @Test + public void testGetTxidForSpentAddress() throws ForeignBlockchainException { + String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA"; + String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress); + + // Reverse the byte order of the txid used by block explorers, to get to big-endian form + byte[] expectedTxidLE = HashCode.fromString("fb386fc8eea0fbf3ea37047726b92c39441652b32d8d62a274331687f7a1eca8").asBytes(); + Bytes.reverse(expectedTxidLE); + String expectedTxidBE = HashCode.fromBytes(expectedTxidLE).toString(); + + assertEquals(expectedTxidBE, txid); + } + + @Test + public void testGetTransactionsForAddress() throws ForeignBlockchainException { + String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA"; + List transactions = pirateChain.getBlockchainProvider() + .getAddressBitcoinyTransactions(p2shAddress, false); + + assertEquals(2, transactions.size()); + } + + @Test + public void testDecodeRawP2SHTransaction() throws TransformationException { + String transactionDataHex = "0400008085202f890002204e00000000000017a9140e8a360d8a54e3d684b7b43c293c6b26ca594abf8700000000000000006e6a4c6b630400738762b17521029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac6782012088a914ab08c6bf2771e0b287303cc14cc02a6bd41a1fad8821029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac6800000000b21e1d0030750000000000000136f025c5ec654eac6aae884c46162aceb7bc3dfd8a800de7adc01c7aef4c85f158ba30e0c4ed45509db91d76f60f6a35f0fd1fcbbac3019d83daa90bc1e05a28b13dcfc12225817a634b86594447e179915dd57accaae2a034d5c66171300e5a91fd576d08f60577a5518a66363349ef2c33771702aa91dfc15ca6bf32506899a2e90488edd6d7c5199476438f10b5c88d49768a5433930a1793aad1303ce0db48af74e0e7edc4104ced5214a94ee0d580fd3b6efbcab863a05c35d320b987fa58968e7f49f6d103dc5a45635d3ac2243eca854256f9ed856d352951402f9b860133ad082e2a1e3330d9923d216917ace5a5bcc350666be0fee214b667faef3df5e23e21d9cabaa90004a11446c20cc0a4756c7712b7b51d3906de8a6ba09114c26aa97ee5f833932a40dcadb830d216c84ded926738b57aaf33a34e0081e5526d8608db1a7008ead9387a3ae9ad4825b2068563363fdaacb860b2cab9e551407b35aa33a46960617df24ab075b9a399f721daa4b16926e44968c3fc15267704024db9f6ca6a09a3f46739f478190da73184e405866a31ee41dec2d633160eedabf9d5d7f361407fb3346ea4a26060b72ca70924745730e8821a22699fb96cff54f0a4270cadfb81193df48e0f573247ebd44b2e10fcd67e3877a4faf028324006d12dbb40b690351ca5ee2514ab5fdbabc21fd9ed9c03f98d45a97f10718d680bd83620187fd9b7093e406cbace69f11a729af72edcbf10eebd8bba7148be19380f6db7a246b9c1fcd238282d050d2000430544893843e5ab2e10d65156e7810bf4eb6401889ab55d7c8ae0cf901239f0e8532a47f161b1771895480173ef64202136d6c04635b78878e12782eabe20dd781f50138a2d7cc55d73070299b8fa1ae9a6a388b9b5c9b01b86feb20d14e7c48b03ff041eac240b2918b0ad1c5fe2f8c03591f6aa0860c87f9d4abd70b842d522629d8fdb7436ea4e2de7b4a2b119ed3d042ed75ad22f2ae6d16abeb3b7a2a7479acf8788184e1058aab5dee0b4ac105f33c57a5a0ba5744ef5de6a65a0ee99988da509c1d0f1e939204619276d29d5f5eecf2850c206dc112e34f06b37341cde63577dfb93c75623f102dd8b3413c31bf38ba04f438df22de81cf42720d6265de2593b6938a82c949c295c546d2343e37104220be8d32172706e205e246e787800357ddc4a10a1b3ec98ae38f6886e40ef2c8e5c3841f60a80d88263e286f66567892d49e63298024d02ce47926a6292cfc03ad03d559cc13a0b1562ea28f9fa1d6496cec47e6743deed4f266bc23b32a3196ba71e29cdce85833129b0777bbdbfd22c45d208ff71d79ca267d6b8556130677147a18b58f72d6628c20bdc87933ae1ec1a7c3a7b1efceb87739b99b785de32bb65e37014e84f905f1d288b927607759184f05cd41ace1f154397eaa4436710328dffcc95dc16399c449e3aabfd87ee160b971def1464c11cdd49609b8c6bf7892badd5483705fcfdfbe0b0345078a5c869245b7dddebef35853dbbefa3527e74d31d9db453251fc0e2b1ef3209e783a696b90ca915bfbaef58161ae8496d16f93d1f05ca9bbf1d2278bddaedf29c2b9e98bcfa5526d229896ed1b50dc71a607805f207eae23fba8a190f57dcbdf1db50e923828970fff049c10f6510e88a9c2fb9e0d1846fadf8ff8c739834cf7570cad2a84a6be4cd78c2344ef1f8745d41517fb825617ecdf0cea098b9f9ed4ba70710e55a0cff793d05b77bbf63f26bc4bec8eabd482c19f1ba6b79a11302763228a3af8bd309b7bad297adccfe6b9033bca82474058ecf2560ff4bbb49f5ea22cb2aa58997013f1c363c9d089264a0e3adbcf1b5a2f8e3440b43f3272e0ada44f4a1f43e549fe6c105752c3eb13b496dbbe65e761e0b0552a004b7854388778449be9726f00e600dcdabfd4aedfb93cb4280c338f23e020903ce1215e4b4158527bc427c8179ec20198a4c0728142a5fca870514a15bf07f9d4eb40e1ca4e67719b23eac04c57276cc7808ac060cb3966940ec4323a0cd3700a1b1189bd31d39e2fe47356781df65547c11e221aca1b80c16304fc33fa070b9b0a5032b967ce4b70257d1d7ff0d6085e0aa1b3f87ceda747c765f095624237ece471bd7c70f5e2e7f8db38036bc6a2a0d7562e0cdd03f05fb7828eb82511c5c843c2577e97151724c1ce5f365fe1cf57ec8ad09a71b5a6732be7bb1b374c26b870b3a2d2b61d889e5d7f2aab13f54774e183950ac7d578b336a658a567056fb26e305875cf4928a9a35e27391f0287f79cc2b6ec77ddc5363001d03ca835ff5aca983805990b233c1d1289fb06083c4c922052c3e4b5575aef9724bcaad5d60a73a7ef00063b0fafa1a5ebdcf745717e30d9293c9b450b70a3c83b902f3d4085e021f0b06ddfcdc2f4258c5f09439dca31f0c54f4998c36e1c53e2cfacdafad4a6a09f8c87ed07c0fd28ec9a83daf222562b3bcab982a29a9480da9166060cf2e8ac69df5def38c8dc6a6638999a559004926402ec597fd3d918a844c6d1ae3e949aafad667a224cbf3441a988f6e0478e513b60b3d51031f1fa4b08d2124e18f585c31429180f18b67b5dd8d33f82caa3e269f3ef7934e9d9192608edda8cc2fe40fd9458a9e832bd6a39e4a5cb0d14fc9f1fda53e127cc1246ab416321c61068c0868ad00cd84577f0347f6b03429b6f7d51982d2b818cdbbb30e89d0d43a2e84f74ec8780e5dd42f8dec0ff80fa56b889604d6617a59b89c50321adf5d8db7ad7bfc1329d8f0aba0408951142619a41ba74a905956407193b666a2e9bd90a5135210274971f4f036453db9218ac5b9e5417a5335abfdc8ed466538a4ac8df4fca5da5f92dbc1833f1b585194119eeb5487925d8ac89eea529ad72550072de6e43efc983c50043d6c1f677c83bcde80d265dd0efe7c12d0bb95178c0a45c7dc55b2ce6d0ef9a897b31b5d18a0dfe0db982c503b58950b471bf4003109f661844aa4197f11cd2d9f1153f5b6bd32b87919c4069ee810eb1bdb70f95f6be42d39915451da63a85979ed6836614874cd72ebfc61df8f61181482531405c6dfde5be00d7ba0c30c751ca511498b6cc14f427d3fdd40f5550236786f933e2913a4bb6e0620efcc191f1d434c2eaeb29a52775b41a32e2344bcf0912eaf674d2c83b8eea225794e4ed88fc761e0ecbd9a2ca747de1415f7bb254c11482500e0603cc470f2d1b09e462247846d714a12c55f9defa18f361576e7d4f39d69d56d786c8297ec8eb9421022aa057cfb02d5cd4a1abd0257f4aa425e148536df05"; + BitcoinyTransaction bitcoinyTransaction = PirateChain.deserializeRawTransaction(transactionDataHex); + assertEquals(0, bitcoinyTransaction.inputs.size()); + assertEquals(2, bitcoinyTransaction.outputs.size()); + assertEquals("a9140e8a360d8a54e3d684b7b43c293c6b26ca594abf87", bitcoinyTransaction.outputs.get(0).scriptPubKey); + assertEquals("6a4c6b630400738762b17521029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac6782012088a914ab08c6bf2771e0b287303cc14cc02a6bd41a1fad8821029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac68", bitcoinyTransaction.outputs.get(1).scriptPubKey); + assertEquals(0, bitcoinyTransaction.locktime); + } + + @Test + public void testDecodeRawRedeemTransaction() throws TransformationException { + String transactionDataHex = "0400008085202f8901efb7c5be4e870464795737109ed02b6c9b5e60e8676f68245c390b4cab09917e00000000d847304402203929739d51dbc4b4d3435cf8b4930101601f670b3e8bcb23fde122b870385fba0220195aed5ed09b7dd342906c67c8da2896d00049bc75127c96724d03b7981fdbd80120baa6e68ffb2ddf19df6fce1b37d7e38b98f3706d5709e46d4f45f71c2e4bb7c301004c6b630436798662b17521037f7e5ab23099885d373da9b9af701cffe06e62225f96f74be3ad6b5aeaad5b82ac6782012088a91429096c1c7a55cc968a98032fbff1cf5f3e98b1c68821037f7e5ab23099885d373da9b9af701cffe06e62225f96f74be3ad6b5aeaad5b82ac68ffffffff0000000000a91e1d00f0d8ffffffffffff000116b2c57a7bcb22e6240ddfd5764ba12bbe589748c8cab6d34d40b2699457a3cbf7bdf6ada43d7649508ecc22f6f6d54d693b6b7434138f8aced6386be251803cd1fb65e7eb2a876ee1d41d74939d398dcb9152e755587325d51ff94b548baf4cff7db34f02e1e419294d8b5ddd55996179da0096751585213b87211626cbf4e4fde3c62a7b40310b375101cbd15983b2b9034fa29293cd1733ee36ade06027005f3e41f93394f15918663b82165aad714fa5851b41f981c77dfdeecdb779472ca30979ec888d33c30fae0d702a2b5fb8a349b5c466338ecc8eef63cb76d04b2ff6ef3b83029f06f875ce0f825fad3538fa5aa3b9db54eaa4f81533ba68af227552707e82e8ad8331b8bb4ba3624a087c4b2ddbe08f21771a0eab22491e444ad663afbe53ec25d2bd9e75295e78b4e9642e65ba04ea4f307a7d3062c4ccf85a71251147abcdd28f7c900b5e8ff19a7d9e24846e9477e277db46f8510a42f8e4a966783818ee8c9fcfa4dc2b1cc7c97b69f8bbd32b5249411b3f0308626e7af28e44a06031e673340c092ec0af3c3b9250c7f00da03ba9634fc0b1a05833330cc1c1a1915ead0a81d62ec420b83936aeff8835100060b2d7e22f9bdb4c679ec38bc3946d66d139c721093902031b0657524736316927588f70817bd117bd757cb64f5cea95d21a5c629e6e784e5f1ea7afce150a4c2e07ab35cca2d8d242de12fc953f2642ffb13fd3e03e25bb7a3993d853263c4a565694554452ae28bf8cb1d83793c3fc6a78a7302b0941c233ba9da1febd5185527727ab3b53aff475408e41924b490f99e532cb24295e1053160077c41fb892695ced2cc73d6bbb18bbd4d0b4d48a3d2c3500c5bb472f404617d00704d0407e51b52284d82e03b901f3acaf99990645ac2ee3a895cc3521126385e3046152666c132c902ba34db629c076396dd6aab718b97bd453d6b7dd3c080528fef4e0482413d448b7f83d1966e10f0fe0fcc5315498abceb84a9e3a6b7ca2e3d0694a92a71c4f1b32fb950acaecbaa72aec501f0dfecb87684b40bbb5abdbc0ae21db978c9112424201c8b711c776c008b87753e9f512be610d594fde628b17bbdbc7dd0c06b479c991d8d7826231f6f0d13529b0ac4f9ae4203fe7fda84c290b120f38e0c0b0c427846a6c3beb7216100a049ba9f014b4fcd88a5a08866ca1e1f3f09f118a77a280486d50241260829de93e8fcd039e0a274767d4388b900e4a2ce4b592f0e416997d7660df0555dee05ddbd484d9f1512bffb683f2d01966b123140bf72c5631e81b73a1f857547ed736c3b2a1f1ac12eddb8d7c9855b523988b72ed0052789154fe244eec1166e557c546f590c0ecc6d8a6988f1f4bb09ba2347cafd2dc1c779c08bb132ddd56d54a339ef6b9dc0f0217fbeed4dcb721394290051e06"; + BitcoinyTransaction bitcoinyTransaction = PirateChain.deserializeRawTransaction(transactionDataHex); + assertEquals(1, bitcoinyTransaction.inputs.size()); + assertEquals(0, bitcoinyTransaction.outputs.size()); + assertEquals("47304402203929739d51dbc4b4d3435cf8b4930101601f670b3e8bcb23fde122b870385fba0220195aed5ed09b7dd342906c67c8da2896d00049bc75127c96724d03b7981fdbd80120baa6e68ffb2ddf19df6fce1b37d7e38b98f3706d5709e46d4f45f71c2e4bb7c301004c6b630436798662b17521037f7e5ab23099885d373da9b9af701cffe06e62225f96f74be3ad6b5aeaad5b82ac6782012088a91429096c1c7a55cc968a98032fbff1cf5f3e98b1c68821037f7e5ab23099885d373da9b9af701cffe06e62225f96f74be3ad6b5aeaad5b82ac68", bitcoinyTransaction.inputs.get(0).scriptSig); + assertEquals("efb7c5be4e870464795737109ed02b6c9b5e60e8676f68245c390b4cab09917e", bitcoinyTransaction.inputs.get(0).outputTxHash); + assertEquals(0, bitcoinyTransaction.inputs.get(0).outputVout); + assertEquals(-1, bitcoinyTransaction.inputs.get(0).sequence); + assertEquals(0, bitcoinyTransaction.locktime); + } + + @Test + public void testDecodeRawRefundTransaction() throws TransformationException { + String transactionDataHex = "0400008085202f8901a8eca1f787163374a2628d2db3521644392cb926770437eaf3fba0eec86f38fb00000000b8483045022100eb217ddf7c671f4d9d74f02b5d5a4340ec69e7e433d6fb839dc1aa0bf8a5059b02206b45f78df09a75ad151bb4bc91e5d555098ceace53cda938bb52ff0ad34dd4a40101514c6b630400738762b17521029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac6782012088a914ab08c6bf2771e0b287303cc14cc02a6bd41a1fad8821029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac68feffffff0000738762111f1d00f0d8ffffffffffff00012a22d50c89ad18ec56b09efb1ce5044c7ad9abcd378f935189168b96210b1e407ede37181c3b51841a78e786a3ba1ef87baac7d7408205a13ec95447e3409c5819f6bbfd31a80ba5d41d367410c5b106788b9e75c49c2916a755b324a10e9210579f97aa8267a490e5e80210e123e657007bf0810294bcc8d73f7cf1b8a1acf2d87edf5dfff0e61423a48fffac84c648bfebec3ff0fa97009ff3d945c6e895b7f5a1e09d50a7920b776b28f5e97dac3eeed25ec0f1ad3cd3ac9e0517fe3c03990fa5303e75b60522e8ffc3ef3493d4460fa14cd8019a96416519408806ee2afa891f1335b6dfd4d1590e9f7939b0718af4758f832d836b928bdfc9329741d906e935316e3d4ec3a24eaf4fc06c321b78bd524d09b4fe09242dd7280ff3f841b56375a6cffd9661db089d1ee6ba4f6e767da194bd4fcd21e860bfa84dd7f431381795ff5998c66b594cf4bfb78c80d044f02e26d9ec0b35e5968ffb98c9be2b196a8a9238116cbd343b3718025c43ae65bacbbc400fd65f957aee17ffb7322aa1dca016a30a5bf21bac910a509eecd54274113c0f24a34786dda9b364e71d1ae0872db8ba06c864cf02bffec15c62ac240de0507230f88679cb7b7c5555c08277840679f89a8ebc2ccc37cdaee5486857293eb2a227191193e03d073ad6a6293a400dabf1ef90215585b78efb6f970490a07382072a0000666b6b70cde068e8841ee4b695088f7a136b44947d03f6961e88d88f79e5e6d7e0b96b61b3d4cc07cd4666fc1e5385915083e0af96ca937320babe0ba43d7914ee501a021fcb6ac02a2232f976a9b123f54ceb1524fe718aa143c1f509bc297419b6cb5277cf16e105a857fd38ce33bc437b52628a8c5eb0f6a4022901f9efddd26c325aa2ad393dd3ec11a75be2884927a322207e7ff8bacb5cdf78503e2051ddac6d2a990471bb9b95e0e865b491d17ec9ee245f8800476f9c4d2475f0ee01561f29edd64dff3d0af2c5847dfcbd498aae3a4c6d497ff9cb359b326599928643567634ef68d3483d81ba364c47e2418c94aa6b70a8cf451f67ba78a194de9e297e1c9be62f2d580e1c3ecd301a3875fcd63fdbf8bb17d0d815c893590fabd76b0bd0f96ab2ff4e43aee4df2f8b07e8cd7342632b9c263dd57df083b96a95276f7576005f840b0d3d95f55f82425381ebaa6cdd64d943c1387695be4206ac749621514d30e460d0260f32c9493d3c35ab99ab5e166fc2b187600996406f96eb0d6c634a683138fcfa31fd028db05233d09c0cdd02a7737e70ebc2c5c5a42c0e9c7ab030020623e8c9d4807dbf8dde6224ca2f75c09aa08d3147d9761c00fa7012218ef325383d64ad35a61e37dc34aae5de1a233ae607c11094508eb39f4813025c4bd121492842b69182c288ec481d4e68ed9ce160e2b4f682e2ff6806"; + BitcoinyTransaction bitcoinyTransaction = PirateChain.deserializeRawTransaction(transactionDataHex); + assertEquals(1, bitcoinyTransaction.inputs.size()); + assertEquals(0, bitcoinyTransaction.outputs.size()); + assertEquals("483045022100eb217ddf7c671f4d9d74f02b5d5a4340ec69e7e433d6fb839dc1aa0bf8a5059b02206b45f78df09a75ad151bb4bc91e5d555098ceace53cda938bb52ff0ad34dd4a40101514c6b630400738762b17521029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac6782012088a914ab08c6bf2771e0b287303cc14cc02a6bd41a1fad8821029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac68", bitcoinyTransaction.inputs.get(0).scriptSig); + assertEquals("a8eca1f787163374a2628d2db3521644392cb926770437eaf3fba0eec86f38fb", bitcoinyTransaction.inputs.get(0).outputTxHash); + assertEquals(0, bitcoinyTransaction.inputs.get(0).outputVout); + assertEquals(-2, bitcoinyTransaction.inputs.get(0).sequence); + assertEquals(1653043968, bitcoinyTransaction.locktime); + } + + @Test + @Ignore(value = "Doesn't work, to be fixed later") + public void testFindHtlcSecret() throws ForeignBlockchainException { + // This actually exists on TEST3 but can take a while to fetch + String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; + + byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes(); + byte[] secret = BitcoinyHTLC.findHtlcSecret(pirateChain, p2shAddress); + + assertNotNull("secret not found", secret); + assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); + } + + @Test + @Ignore(value = "Needs adapting for Pirate Chain") + public void testBuildSpend() { + String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; + + String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; + long amount = 1000L; + + Transaction transaction = pirateChain.buildSpend(xprv58, recipient, amount); + assertNotNull("insufficient funds", transaction); + + // Check spent key caching doesn't affect outcome + + transaction = pirateChain.buildSpend(xprv58, recipient, amount); + assertNotNull("insufficient funds", transaction); + } + + @Test + @Ignore(value = "Needs adapting for Pirate Chain") + public void testGetWalletBalance() throws ForeignBlockchainException { + String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; + + Long balance = pirateChain.getWalletBalance(xprv58); + + assertNotNull(balance); + + System.out.println(pirateChain.format(balance)); + + // Check spent key caching doesn't affect outcome + + Long repeatBalance = pirateChain.getWalletBalance(xprv58); + + assertNotNull(repeatBalance); + + System.out.println(pirateChain.format(repeatBalance)); + + assertEquals(balance, repeatBalance); + } + + @Test + @Ignore(value = "Needs adapting for Pirate Chain") + public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { + String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; + + String address = pirateChain.getUnusedReceiveAddress(xprv58); + + assertNotNull(address); + + System.out.println(address); + } + +} diff --git a/src/test/java/org/qortal/test/crosschain/RavencoinTests.java b/src/test/java/org/qortal/test/crosschain/RavencoinTests.java index afcbfe95..866c41a2 100644 --- a/src/test/java/org/qortal/test/crosschain/RavencoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/RavencoinTests.java @@ -81,7 +81,7 @@ public class RavencoinTests extends Common { } @Test - public void testGetWalletBalance() { + public void testGetWalletBalance() throws ForeignBlockchainException { String xprv58 = "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7"; Long balance = ravencoin.getWalletBalance(xprv58); diff --git a/src/test/java/org/qortal/test/crosschain/apps/GetWalletTransactions.java b/src/test/java/org/qortal/test/crosschain/apps/GetWalletTransactions.java index 7a880b1a..2c1edc10 100644 --- a/src/test/java/org/qortal/test/crosschain/apps/GetWalletTransactions.java +++ b/src/test/java/org/qortal/test/crosschain/apps/GetWalletTransactions.java @@ -75,7 +75,7 @@ public class GetWalletTransactions { System.out.println(String.format("Found %d transaction%s", transactions.size(), (transactions.size() != 1 ? "s" : ""))); - for (SimpleTransaction transaction : transactions.stream().sorted(Comparator.comparingInt(SimpleTransaction::getTimestamp)).collect(Collectors.toList())) + for (SimpleTransaction transaction : transactions.stream().sorted(Comparator.comparingLong(SimpleTransaction::getTimestamp)).collect(Collectors.toList())) System.out.println(String.format("%s", transaction)); } diff --git a/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java new file mode 100644 index 00000000..f9ac9de1 --- /dev/null +++ b/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java @@ -0,0 +1,771 @@ +package org.qortal.test.crosschain.piratechainv3; + +import com.google.common.hash.HashCode; +import com.google.common.primitives.Bytes; +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.Account; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.asset.Asset; +import org.qortal.block.Block; +import org.qortal.crosschain.AcctMode; +import org.qortal.crosschain.PirateChainACCTv3; +import org.qortal.crypto.Crypto; +import org.qortal.data.at.ATData; +import org.qortal.data.at.ATStateData; +import org.qortal.data.crosschain.CrossChainTradeData; +import org.qortal.data.transaction.BaseTransactionData; +import org.qortal.data.transaction.DeployAtTransactionData; +import org.qortal.data.transaction.MessageTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.group.Group; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.BlockUtils; +import org.qortal.test.common.Common; +import org.qortal.test.common.TransactionUtils; +import org.qortal.transaction.DeployAtTransaction; +import org.qortal.transaction.MessageTransaction; +import org.qortal.utils.Amounts; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.Function; + +import static org.junit.Assert.*; + +public class PirateChainACCTv3Tests extends Common { + + public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); + public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a + public static final byte[] pirateChainPublicKey = HashCode.fromString("aabb00bb11bb22bb33bb44bb55bb66bb77bb88bb99cc00cc11cc22cc33cc44cc55").asBytes(); // 33 bytes + public static final int tradeTimeout = 20; // blocks + public static final long redeemAmount = 80_40200000L; + public static final long fundingAmount = 123_45600000L; + public static final long arrrAmount = 864200L; // 0.00864200 ARRR + + private static final Random RANDOM = new Random(); + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @Test + public void testCompile() { + PrivateKeyAccount tradeAccount = createTradeAccount(null); + + byte[] creationBytes = PirateChainACCTv3.buildQortalAT(tradeAccount.getAddress(), pirateChainPublicKey, redeemAmount, arrrAmount, tradeTimeout); + assertNotNull(creationBytes); + + System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + } + + @Test + public void testDeploy() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + + long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = fundingAmount; + actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); + + assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = partnersInitialBalance; + actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); + + // Test orphaning + BlockUtils.orphanLastBlock(repository); + + expectedBalance = deployersInitialBalance; + actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = 0; + actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); + + assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = partnersInitialBalance; + actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + } + } + + @SuppressWarnings("unused") + @Test + public void testOfferCancel() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + + // Send creator's address to AT, instead of typical partner's address + byte[] messageData = PirateChainACCTv3.getInstance().buildCancelMessage(deployer.getAddress()); + MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); + long messageFee = messageTransaction.getTransactionData().getFee(); + + // AT should process 'cancel' message in next block + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in CANCELLED mode + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.CANCELLED, tradeData.mode); + + // Check balances + long expectedMinimumBalance = deployersPostDeploymentBalance; + long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; + + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); + assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); + + // Test orphaning + BlockUtils.orphanLastBlock(repository); + + // Check balances + long expectedBalance = deployersPostDeploymentBalance - messageFee; + actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); + } + } + + @SuppressWarnings("unused") + @Test + public void testOfferCancelInvalidLength() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + + // Instead of sending creator's address to AT, send too-short/invalid message + byte[] messageData = new byte[7]; + RANDOM.nextBytes(messageData); + MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); + long messageFee = messageTransaction.getTransactionData().getFee(); + + // AT should process 'cancel' message in next block + // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in CANCELLED mode + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.CANCELLED, tradeData.mode); + } + } + + @SuppressWarnings("unused") + @Test + public void testTradingInfoProcessing() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + Block postDeploymentBlock = BlockUtils.mintBlock(repository); + int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); + + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + + describeAt(repository, atAddress); + + System.out.println(String.format("pirateChainPublicKey: %s", HashCode.fromBytes(pirateChainPublicKey))); + + ATData atData = repository.getATRepository().fromATAddress(atAddress); + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + + // AT should be in TRADE mode + assertEquals(AcctMode.TRADING, tradeData.mode); + + // Check hashOfSecretA was extracted correctly + assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); + + // Check trade partner Qortal address was extracted correctly + assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); + + // Check trade partner's Litecoin PKH was extracted correctly + assertTrue(Arrays.equals(pirateChainPublicKey, tradeData.partnerForeignPKH)); + + // Test orphaning + BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); + + // Check balances + long expectedBalance = deployersPostDeploymentBalance; + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); + } + } + + // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) + @SuppressWarnings("unused") + @Test + public void testIncorrectTradeSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT BUT NOT FROM AT CREATOR + byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); + + BlockUtils.mintBlock(repository); + + long expectedBalance = partnersInitialBalance; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); + + describeAt(repository, atAddress); + + ATData atData = repository.getATRepository().fromATAddress(atAddress); + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + + // AT should still be in OFFER mode + assertEquals(AcctMode.OFFERING, tradeData.mode); + } + } + + @SuppressWarnings("unused") + @Test + public void testAutomaticTradeRefund() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + Block postDeploymentBlock = BlockUtils.mintBlock(repository); + int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); + + // Check refund + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); + + describeAt(repository, atAddress); + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in REFUNDED mode + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.REFUNDED, tradeData.mode); + + // Test orphaning + BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); + + // Check balances + long expectedBalance = deployersPostDeploymentBalance; + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); + } + } + + @SuppressWarnings("unused") + @Test + public void testCorrectSecretCorrectSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + // Give AT time to process message + BlockUtils.mintBlock(repository); + + // Send correct secret to AT, from correct account + messageData = PirateChainACCTv3.buildRedeemMessage(secretA, partner.getAddress()); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); + + // AT should send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in REDEEMED mode + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.REDEEMED, tradeData.mode); + + // Check balances + long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); + + // Orphan redeem + BlockUtils.orphanLastBlock(repository); + + // Check balances + expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); + actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); + + // Check AT state + ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); + + assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); + } + } + + @SuppressWarnings("unused") + @Test + public void testCorrectSecretIncorrectSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + // Give AT time to process message + BlockUtils.mintBlock(repository); + + // Send correct secret to AT, but from wrong account + messageData = PirateChainACCTv3.buildRedeemMessage(secretA, partner.getAddress()); + messageTransaction = sendMessage(repository, bystander, messageData, atAddress); + + // AT should NOT send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is NOT finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertFalse(atData.getIsFinished()); + + // AT should still be in TRADE mode + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.TRADING, tradeData.mode); + + // Check balances + long expectedBalance = partnersInitialBalance; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); + + // Check eventual refund + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); + } + } + + @SuppressWarnings("unused") + @Test + public void testIncorrectSecretCorrectSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + // Give AT time to process message + BlockUtils.mintBlock(repository); + + // Send incorrect secret to AT, from correct account + byte[] wrongSecret = new byte[32]; + RANDOM.nextBytes(wrongSecret); + messageData = PirateChainACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress()); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); + + // AT should NOT send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is NOT finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertFalse(atData.getIsFinished()); + + // AT should still be in TRADE mode + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.TRADING, tradeData.mode); + + long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); + + // Check eventual refund + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); + } + } + + @SuppressWarnings("unused") + @Test + public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + // Give AT time to process message + BlockUtils.mintBlock(repository); + + // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length + messageData = Bytes.concat(secretA); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); + + // AT should NOT send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is NOT finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertFalse(atData.getIsFinished()); + + // AT should be in TRADING mode + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.TRADING, tradeData.mode); + } + } + + @SuppressWarnings("unused") + @Test + public void testDescribeDeployed() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + + List executableAts = repository.getATRepository().getAllExecutableATs(); + + for (ATData atData : executableAts) { + String atAddress = atData.getATAddress(); + byte[] codeBytes = atData.getCodeBytes(); + byte[] codeHash = Crypto.digest(codeBytes); + + System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", + atAddress, + codeBytes.length, + (codeBytes.length != 1 ? "s": ""), + HashCode.fromBytes(codeHash))); + + // Not one of ours? + if (!Arrays.equals(codeHash, PirateChainACCTv3.CODE_BYTES_HASH)) + continue; + + describeAt(repository, atAddress); + } + } + } + + private int calcTestLockTimeA(long messageTimestamp) { + return (int) (messageTimestamp / 1000L + tradeTimeout * 60); + } + + private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { + byte[] creationBytes = PirateChainACCTv3.buildQortalAT(tradeAddress, pirateChainPublicKey, redeemAmount, arrrAmount, tradeTimeout); + + long txTimestamp = System.currentTimeMillis(); + byte[] lastReference = deployer.getLastReference(); + + if (lastReference == null) { + System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); + System.exit(2); + } + + Long fee = null; + String name = "QORT-ARRR cross-chain trade"; + String description = String.format("Qortal-PirateChain cross-chain trade"); + String atType = "ACCT"; + String tags = "QORT-ARRR ACCT"; + + BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); + TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); + + DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); + + fee = deployAtTransaction.calcRecommendedFee(); + deployAtTransactionData.setFee(fee); + + TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); + + return deployAtTransaction; + } + + private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { + long txTimestamp = System.currentTimeMillis(); + byte[] lastReference = sender.getLastReference(); + + if (lastReference == null) { + System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); + System.exit(2); + } + + Long fee = null; + int version = 4; + int nonce = 0; + long amount = 0; + Long assetId = null; // because amount is zero + + BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); + TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); + + MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); + + fee = messageTransaction.calcRecommendedFee(); + messageTransactionData.setFee(fee); + + TransactionUtils.signAndMint(repository, messageTransactionData, sender); + + return messageTransaction; + } + + private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + int refundTimeout = tradeTimeout / 2 + 1; // close enough + + // AT should automatically refund deployer after 'refundTimeout' blocks + for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) + BlockUtils.mintBlock(repository); + + // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range + long expectedMinimumBalance = deployersPostDeploymentBalance; + long expectedMaximumBalance = deployersInitialBalance - deployAtFee; + + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); + assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); + } + + private void describeAt(Repository repository, String atAddress) throws DataException { + ATData atData = repository.getATRepository().fromATAddress(atAddress); + CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); + + Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); + int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); + + System.out.print(String.format("%s:\n" + + "\tmode: %s\n" + + "\tcreator: %s,\n" + + "\tcreation timestamp: %s,\n" + + "\tcurrent balance: %s QORT,\n" + + "\tis finished: %b,\n" + + "\tredeem payout: %s QORT,\n" + + "\texpected ARRR: %s ARRR,\n" + + "\tcurrent block height: %d,\n", + tradeData.qortalAtAddress, + tradeData.mode, + tradeData.qortalCreator, + epochMilliFormatter.apply(tradeData.creationTimestamp), + Amounts.prettyAmount(tradeData.qortBalance), + atData.getIsFinished(), + Amounts.prettyAmount(tradeData.qortAmount), + Amounts.prettyAmount(tradeData.expectedForeignAmount), + currentBlockHeight)); + + if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { + System.out.println(String.format("\trefund timeout: %d minutes,\n" + + "\trefund height: block %d,\n" + + "\tHASH160 of secret-A: %s,\n" + + "\tPirate Chain P2SH-A nLockTime: %d (%s),\n" + + "\ttrade partner: %s\n" + + "\tpartner's receiving address: %s", + tradeData.refundTimeout, + tradeData.tradeRefundHeight, + HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), + tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), + tradeData.qortalPartnerAddress, + tradeData.qortalPartnerReceivingAddress)); + } + } + + private PrivateKeyAccount createTradeAccount(Repository repository) { + // We actually use a known test account with funds to avoid PoW compute + return Common.getTestAccount(repository, "alice"); + } + +} diff --git a/src/test/java/org/qortal/test/group/AdminTests.java b/src/test/java/org/qortal/test/group/AdminTests.java index a39b23d7..8cf83c29 100644 --- a/src/test/java/org/qortal/test/group/AdminTests.java +++ b/src/test/java/org/qortal/test/group/AdminTests.java @@ -135,7 +135,8 @@ public class AdminTests extends Common { assertNotSame(ValidationResult.OK, result); // Attempt to ban Bob - result = groupBan(repository, alice, groupId, bob.getAddress()); + int timeToLive = 0; + result = groupBan(repository, alice, groupId, bob.getAddress(), timeToLive); // Should be OK assertEquals(ValidationResult.OK, result); @@ -158,7 +159,7 @@ public class AdminTests extends Common { assertTrue(isMember(repository, bob.getAddress(), groupId)); // Attempt to ban Bob - result = groupBan(repository, alice, groupId, bob.getAddress()); + result = groupBan(repository, alice, groupId, bob.getAddress(), timeToLive); // Should be OK assertEquals(ValidationResult.OK, result); @@ -205,6 +206,144 @@ public class AdminTests extends Common { } } + @Test + public void testGroupBanMemberWithExpiry() throws DataException, InterruptedException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Create group + int groupId = createGroup(repository, alice, "open-group", true); + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to cancel non-existent Bob ban + ValidationResult result = cancelGroupBan(repository, alice, groupId, bob.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Attempt to ban Bob for 2 seconds + int timeToLive = 2; + result = groupBan(repository, alice, groupId, bob.getAddress(), timeToLive); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob no longer a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Wait for 2 seconds to pass + Thread.sleep(2000L); + + // Bob attempts to rejoin again + result = joinGroup(repository, bob, groupId); + // Should be OK, as the ban has expired + assertSame(ValidationResult.OK, result); + + // Confirm Bob is now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Orphan last block (Bob join) + BlockUtils.orphanLastBlock(repository); + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Orphan last block (Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed group-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Bob to join + result = joinGroup(repository, bob, groupId); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + + // Attempt to ban Bob for 2 seconds + result = groupBan(repository, alice, groupId, bob.getAddress(), 2); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob no longer a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Wait for 2 seconds to pass + Thread.sleep(2000L); + + // Cancel Bob's ban + result = cancelGroupBan(repository, alice, groupId, bob.getAddress()); + // Should NOT be OK, as ban has already expired + assertNotSame(ValidationResult.OK, result); + + // Confirm Bob still not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should be OK, as no longer banned + assertSame(ValidationResult.OK, result); + + // Confirm Bob is now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + + // Attempt to ban Bob for 10 seconds + result = groupBan(repository, alice, groupId, bob.getAddress(), 10); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob no longer a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK, as ban still exists + assertNotSame(ValidationResult.OK, result); + + // Cancel Bob's ban + result = cancelGroupBan(repository, alice, groupId, bob.getAddress()); + // Should be OK, as ban still exists + assertEquals(ValidationResult.OK, result); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should be OK, as no longer banned + assertEquals(ValidationResult.OK, result); + + // Orphan last block (Bob join) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed join-group transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Orphan last block (Cancel Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed cancel-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Orphan last block (Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed group-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + } + } + @Test public void testGroupBanAdmin() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { @@ -226,7 +365,8 @@ public class AdminTests extends Common { assertTrue(isAdmin(repository, bob.getAddress(), groupId)); // Attempt to ban Bob - result = groupBan(repository, alice, groupId, bob.getAddress()); + int timeToLive = 0; + result = groupBan(repository, alice, groupId, bob.getAddress(), timeToLive); // Should be OK assertEquals(ValidationResult.OK, result); @@ -272,12 +412,12 @@ public class AdminTests extends Common { assertTrue(isAdmin(repository, bob.getAddress(), groupId)); // Have Alice (owner) try to ban herself! - result = groupBan(repository, alice, groupId, alice.getAddress()); + result = groupBan(repository, alice, groupId, alice.getAddress(), timeToLive); // Should NOT be OK assertNotSame(ValidationResult.OK, result); // Have Bob try to ban Alice (owner) - result = groupBan(repository, bob, groupId, alice.getAddress()); + result = groupBan(repository, bob, groupId, alice.getAddress(), timeToLive); // Should NOT be OK assertNotSame(ValidationResult.OK, result); } @@ -316,8 +456,8 @@ public class AdminTests extends Common { return result; } - private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { - GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", 0); + private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member, int timeToLive) throws DataException { + GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", timeToLive); ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); if (result == ValidationResult.OK) diff --git a/src/test/java/org/qortal/test/group/DevGroupAdminTests.java b/src/test/java/org/qortal/test/group/DevGroupAdminTests.java new file mode 100644 index 00000000..131359c6 --- /dev/null +++ b/src/test/java/org/qortal/test/group/DevGroupAdminTests.java @@ -0,0 +1,388 @@ +package org.qortal.test.group; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.data.transaction.*; +import org.qortal.group.Group; +import org.qortal.group.Group.ApprovalThreshold; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.BlockUtils; +import org.qortal.test.common.Common; +import org.qortal.test.common.GroupUtils; +import org.qortal.test.common.TransactionUtils; +import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.Transaction; +import org.qortal.transaction.Transaction.ValidationResult; +import org.qortal.utils.Base58; + +import static org.junit.Assert.*; + +/** + * Dev group admin tests + * + * The dev group (ID 1) is owned by the null account with public key 11111111111111111111111111111111 + * To regain access to otherwise blocked owner-based rules, it has different validation logic + * which applies to groups with this same null owner. + * + * The main difference is that approval is required for certain transaction types relating to + * null-owned groups. This allows existing admins to approve updates to the group (using group's + * approval threshold) instead of these actions being performed by the owner. + * + * Since these apply to all null-owned groups, this allows anyone to update their group to + * the null owner if they want to take advantage of this decentralized approval system. + * + * Currently, the affected transaction types are: + * - AddGroupAdminTransaction + * - RemoveGroupAdminTransaction + * + * This same approach could ultimately be applied to other group transactions too. + */ +public class DevGroupAdminTests extends Common { + + private static final int DEV_GROUP_ID = 1; + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @After + public void afterTest() throws DataException { + Common.orphanCheck(); + } + + @Test + public void testGroupKickMember() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Dev group + int groupId = DEV_GROUP_ID; + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to kick Bob + ValidationResult result = groupKick(repository, alice, groupId, bob.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Alice to invite Bob, as it's a closed group + groupInvite(repository, alice, groupId, bob.getAddress(), 3600); + + // Bob to join + joinGroup(repository, bob, groupId); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to kick Bob + result = groupKick(repository, alice, groupId, bob.getAddress()); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob no longer a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + } + } + + @Test + public void testGroupKickAdmin() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Dev group + int groupId = DEV_GROUP_ID; + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Alice to invite Bob, as it's a closed group + groupInvite(repository, alice, groupId, bob.getAddress(), 3600); + + // Bob to join + joinGroup(repository, bob, groupId); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Promote Bob to admin + TransactionData addGroupAdminTransactionData = addGroupAdmin(repository, alice, groupId, bob.getAddress()); + + // Confirm transaction needs approval, and hasn't been approved + Transaction.ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, addGroupAdminTransactionData.getSignature()); + assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus); + + // Have Alice approve Bob's approval-needed transaction + GroupUtils.approveTransaction(repository, "alice", addGroupAdminTransactionData.getSignature(), true); + + // Mint a block so that the transaction becomes approved + BlockUtils.mintBlock(repository); + + // Confirm transaction is approved + approvalStatus = GroupUtils.getApprovalStatus(repository, addGroupAdminTransactionData.getSignature()); + assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.APPROVED, approvalStatus); + + // Confirm Bob is now admin + assertTrue(isAdmin(repository, bob.getAddress(), groupId)); + + // Attempt to kick Bob + ValidationResult result = groupKick(repository, alice, groupId, bob.getAddress()); + // Shouldn't be allowed + assertEquals(ValidationResult.INVALID_GROUP_OWNER, result); + + // Confirm Bob is still a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Confirm Bob still an admin + assertTrue(isAdmin(repository, bob.getAddress(), groupId)); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Confirm Bob no longer an admin (ADD_GROUP_ADMIN no longer approved) + assertFalse(isAdmin(repository, bob.getAddress(), groupId)); + + // Have Alice try to kick herself! + result = groupKick(repository, alice, groupId, alice.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Have Bob try to kick Alice + result = groupKick(repository, bob, groupId, alice.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + } + } + + @Test + public void testGroupBanMember() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Dev group + int groupId = DEV_GROUP_ID; + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to cancel non-existent Bob ban + ValidationResult result = cancelGroupBan(repository, alice, groupId, bob.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Attempt to ban Bob + result = groupBan(repository, alice, groupId, bob.getAddress()); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Orphan last block (Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed group-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Alice to invite Bob, as it's a closed group + groupInvite(repository, alice, groupId, bob.getAddress(), 3600); + + // Bob to join + result = joinGroup(repository, bob, groupId); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // Attempt to ban Bob + result = groupBan(repository, alice, groupId, bob.getAddress()); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Confirm Bob no longer a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Cancel Bob's ban + result = cancelGroupBan(repository, alice, groupId, bob.getAddress()); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Orphan last block (Bob join) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed join-group transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Orphan last block (Cancel Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed cancel-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Bob attempts to rejoin + result = joinGroup(repository, bob, groupId); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Orphan last block (Bob ban) + BlockUtils.orphanLastBlock(repository); + // Delete unconfirmed group-ban transaction + TransactionUtils.deleteUnconfirmedTransactions(repository); + + // Confirm Bob now a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + } + } + + @Test + public void testGroupBanAdmin() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Dev group + int groupId = DEV_GROUP_ID; + + // Confirm Bob is not a member + assertFalse(isMember(repository, bob.getAddress(), groupId)); + + // Alice to invite Bob, as it's a closed group + groupInvite(repository, alice, groupId, bob.getAddress(), 3600); + + // Bob to join + ValidationResult result = joinGroup(repository, bob, groupId); + // Should be OK + assertEquals(ValidationResult.OK, result); + + // Promote Bob to admin + TransactionData addGroupAdminTransactionData = addGroupAdmin(repository, alice, groupId, bob.getAddress()); + + // Confirm transaction needs approval, and hasn't been approved + Transaction.ApprovalStatus approvalStatus = GroupUtils.getApprovalStatus(repository, addGroupAdminTransactionData.getSignature()); + assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus); + + // Have Alice approve Bob's approval-needed transaction + GroupUtils.approveTransaction(repository, "alice", addGroupAdminTransactionData.getSignature(), true); + + // Mint a block so that the transaction becomes approved + BlockUtils.mintBlock(repository); + + // Confirm transaction is approved + approvalStatus = GroupUtils.getApprovalStatus(repository, addGroupAdminTransactionData.getSignature()); + assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.APPROVED, approvalStatus); + + // Confirm Bob is now admin + assertTrue(isAdmin(repository, bob.getAddress(), groupId)); + + // Attempt to ban Bob + result = groupBan(repository, alice, groupId, bob.getAddress()); + // .. but we can't, because Bob is an admin and the group has no owner + assertEquals(ValidationResult.INVALID_GROUP_OWNER, result); + + // Confirm Bob still a member + assertTrue(isMember(repository, bob.getAddress(), groupId)); + + // ... and still an admin + assertTrue(isAdmin(repository, bob.getAddress(), groupId)); + + // Have Alice try to ban herself! + result = groupBan(repository, alice, groupId, alice.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + + // Have Bob try to ban Alice + result = groupBan(repository, bob, groupId, alice.getAddress()); + // Should NOT be OK + assertNotSame(ValidationResult.OK, result); + } + } + + + private ValidationResult joinGroup(Repository repository, PrivateKeyAccount joiner, int groupId) throws DataException { + JoinGroupTransactionData transactionData = new JoinGroupTransactionData(TestTransaction.generateBase(joiner), groupId); + ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, joiner); + + if (result == ValidationResult.OK) + BlockUtils.mintBlock(repository); + + return result; + } + + private void groupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException { + GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin), groupId, invitee, timeToLive); + TransactionUtils.signAndMint(repository, transactionData, admin); + } + + private ValidationResult groupKick(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { + GroupKickTransactionData transactionData = new GroupKickTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing"); + ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); + + if (result == ValidationResult.OK) + BlockUtils.mintBlock(repository); + + return result; + } + + private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { + GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", 0); + ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); + + if (result == ValidationResult.OK) + BlockUtils.mintBlock(repository); + + return result; + } + + private ValidationResult cancelGroupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException { + CancelGroupBanTransactionData transactionData = new CancelGroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member); + ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin); + + if (result == ValidationResult.OK) + BlockUtils.mintBlock(repository); + + return result; + } + + private TransactionData addGroupAdmin(Repository repository, PrivateKeyAccount owner, int groupId, String member) throws DataException { + AddGroupAdminTransactionData transactionData = new AddGroupAdminTransactionData(TestTransaction.generateBase(owner), groupId, member); + transactionData.setTxGroupId(groupId); + TransactionUtils.signAndMint(repository, transactionData, owner); + return transactionData; + } + + private boolean isMember(Repository repository, String address, int groupId) throws DataException { + return repository.getGroupRepository().memberExists(groupId, address); + } + + private boolean isAdmin(Repository repository, String address, int groupId) throws DataException { + return repository.getGroupRepository().adminExists(groupId, address); + } + +} diff --git a/src/test/java/org/qortal/test/minting/BlockTimestampTests.java b/src/test/java/org/qortal/test/minting/BlockTimestampTests.java new file mode 100644 index 00000000..0f91408f --- /dev/null +++ b/src/test/java/org/qortal/test/minting/BlockTimestampTests.java @@ -0,0 +1,63 @@ +package org.qortal.test.minting; + +import org.junit.Before; +import org.junit.Test; +import org.qortal.block.Block; +import org.qortal.data.block.BlockData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.BlockUtils; +import org.qortal.test.common.Common; +import org.qortal.transform.Transformer; +import org.qortal.utils.NTP; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class BlockTimestampTests extends Common { + + private static class BlockTimestampDataPoint { + public byte[] minterPublicKey; + public int minterAccountLevel; + public long blockTimestamp; + } + + private static final Random RANDOM = new Random(); + + @Before + public void beforeTest() throws DataException { + Common.useSettings("test-settings-v2-block-timestamps.json"); + NTP.setFixedOffset(0L); + } + + @Test + public void testTimestamps() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + Block parentBlock = BlockUtils.mintBlock(repository); + BlockData parentBlockData = parentBlock.getBlockData(); + + // Generate lots of test minters + List dataPoints = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + BlockTimestampDataPoint dataPoint = new BlockTimestampDataPoint(); + + dataPoint.minterPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + RANDOM.nextBytes(dataPoint.minterPublicKey); + + dataPoint.minterAccountLevel = RANDOM.nextInt(5) + 5; + + dataPoint.blockTimestamp = Block.calcTimestamp(parentBlockData, dataPoint.minterPublicKey, dataPoint.minterAccountLevel); + + System.out.printf("[%d] level %d, blockTimestamp %d - parentTimestamp %d = %d%n", + i, + dataPoint.minterAccountLevel, + dataPoint.blockTimestamp, + parentBlockData.getTimestamp(), + dataPoint.blockTimestamp - parentBlockData.getTimestamp() + ); + } + } + } +} diff --git a/src/test/java/org/qortal/test/minting/RewardShareTests.java b/src/test/java/org/qortal/test/minting/RewardShareTests.java index cde3c2ff..b5ac5e59 100644 --- a/src/test/java/org/qortal/test/minting/RewardShareTests.java +++ b/src/test/java/org/qortal/test/minting/RewardShareTests.java @@ -177,4 +177,143 @@ public class RewardShareTests extends Common { } } + @Test + public void testCreateRewardSharesBeforeReduction() throws DataException { + final int sharePercent = 0; + + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Create 6 reward shares + for (int i=0; i<6; i++) { + AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } + + // 7th reward share should fail because we've reached the limit (and we're not yet requiring a self share) + AssertionError assertionError = null; + try { + AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } catch (AssertionError e) { + assertionError = e; + } + assertNotNull("Transaction should be invalid", assertionError); + assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES")); + } + } + + @Test + public void testCreateRewardSharesAfterReduction() throws DataException { + Common.useSettings("test-settings-v2-reward-shares.json"); + + final int sharePercent = 0; + + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Create 2 reward shares + for (int i=0; i<2; i++) { + AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } + + // 3rd reward share should fail because we've reached the limit (and we haven't got a self share) + AssertionError assertionError = null; + try { + AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } catch (AssertionError e) { + assertionError = e; + } + assertNotNull("Transaction should be invalid", assertionError); + assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES")); + } + } + + @Test + public void testCreateSelfAndRewardSharesAfterReduction() throws DataException { + Common.useSettings("test-settings-v2-reward-shares.json"); + + final int sharePercent = 0; + + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Create 2 reward shares + for (int i=0; i<2; i++) { + AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } + + // 3rd reward share should fail because we've reached the limit (and we haven't got a self share) + AssertionError assertionError = null; + try { + AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } catch (AssertionError e) { + assertionError = e; + } + assertNotNull("Transaction should be invalid", assertionError); + assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES")); + + // Now create a self share, which should succeed as we have space for it + AccountUtils.rewardShare(repository, dilbertAccount, dilbertAccount, sharePercent); + + // 4th reward share should fail because we've reached the limit (including the self share) + assertionError = null; + try { + AccountUtils.rewardShare(repository, dilbertAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } catch (AssertionError e) { + assertionError = e; + } + assertNotNull("Transaction should be invalid", assertionError); + assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES")); + } + } + + @Test + public void testCreateFounderRewardSharesBeforeReduction() throws DataException { + final int sharePercent = 0; + + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount aliceFounderAccount = Common.getTestAccount(repository, "alice"); + + // Create 5 reward shares (not 6, because alice already starts with a self reward share in the genesis block) + for (int i=0; i<5; i++) { + AccountUtils.rewardShare(repository, aliceFounderAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } + + // 6th reward share should fail + AssertionError assertionError = null; + try { + AccountUtils.rewardShare(repository, aliceFounderAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } catch (AssertionError e) { + assertionError = e; + } + assertNotNull("Transaction should be invalid", assertionError); + assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES")); + } + } + + @Test + public void testCreateFounderRewardSharesAfterReduction() throws DataException { + Common.useSettings("test-settings-v2-reward-shares.json"); + + final int sharePercent = 0; + + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount aliceFounderAccount = Common.getTestAccount(repository, "alice"); + + // Create 5 reward shares (not 6, because alice already starts with a self reward share in the genesis block) + for (int i=0; i<5; i++) { + AccountUtils.rewardShare(repository, aliceFounderAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } + + // 6th reward share should fail + AssertionError assertionError = null; + try { + AccountUtils.rewardShare(repository, aliceFounderAccount, Common.generateRandomSeedAccount(repository), sharePercent); + } catch (AssertionError e) { + assertionError = e; + } + assertNotNull("Transaction should be invalid", assertionError); + assertTrue("Transaction should be invalid due to reaching maximum reward shares", assertionError.getMessage().contains("MAXIMUM_REWARD_SHARES")); + } + } + } diff --git a/src/test/java/org/qortal/test/minting/RewardTests.java b/src/test/java/org/qortal/test/minting/RewardTests.java index f7970ace..1689db5b 100644 --- a/src/test/java/org/qortal/test/minting/RewardTests.java +++ b/src/test/java/org/qortal/test/minting/RewardTests.java @@ -3,10 +3,9 @@ package org.qortal.test.minting; import static org.junit.Assert.*; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; +import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.After; @@ -14,6 +13,7 @@ import org.junit.Before; import org.junit.Test; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; +import org.qortal.block.Block; import org.qortal.block.BlockChain; import org.qortal.block.BlockChain.RewardByHeight; import org.qortal.controller.BlockMinter; @@ -109,7 +109,7 @@ public class RewardTests extends Common { public void testLegacyQoraReward() throws DataException { Common.useSettings("test-settings-v2-qora-holder-extremes.json"); - long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare(); + long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(1); BigInteger qoraHoldersShareBI = BigInteger.valueOf(qoraHoldersShare); long qoraPerQort = BlockChain.getInstance().getQoraPerQortReward(); @@ -190,6 +190,47 @@ public class RewardTests extends Common { } } + @Test + public void testLegacyQoraRewardReduction() throws DataException { + Common.useSettings("test-settings-v2-qora-holder-reduction.json"); + + // Make sure that the QORA share reduces between blocks 4 and 5 + assertTrue(BlockChain.getInstance().getQoraHoldersShareAtHeight(5) < BlockChain.getInstance().getQoraHoldersShareAtHeight(4)); + + // Keep track of balance deltas at each height + Map chloeQortBalanceDeltaAtEachHeight = new HashMap<>(); + + try (final Repository repository = RepositoryManager.getRepository()) { + Map> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA); + long chloeLastQortBalance = initialBalances.get("chloe").get(Asset.QORT); + + for (int i=2; i<=10; i++) { + + Block block = BlockUtils.mintBlock(repository); + + // Add to map of balance deltas at each height + long chloeNewQortBalance = AccountUtils.getBalance(repository, "chloe", Asset.QORT); + chloeQortBalanceDeltaAtEachHeight.put(block.getBlockData().getHeight(), chloeNewQortBalance - chloeLastQortBalance); + chloeLastQortBalance = chloeNewQortBalance; + } + + // Ensure blocks 2-4 paid out the same rewards to Chloe + assertEquals(chloeQortBalanceDeltaAtEachHeight.get(2), chloeQortBalanceDeltaAtEachHeight.get(4)); + + // Ensure block 5 paid a lower reward + assertTrue(chloeQortBalanceDeltaAtEachHeight.get(5) < chloeQortBalanceDeltaAtEachHeight.get(4)); + + // Check that the reward was 20x lower + assertTrue(chloeQortBalanceDeltaAtEachHeight.get(5) == chloeQortBalanceDeltaAtEachHeight.get(4) / 20); + + // Orphan to block 4 and ensure that Chloe's balance hasn't been incorrectly affected by the reward reduction + BlockUtils.orphanToBlock(repository, 4); + long expectedChloeQortBalance = initialBalances.get("chloe").get(Asset.QORT) + chloeQortBalanceDeltaAtEachHeight.get(2) + + chloeQortBalanceDeltaAtEachHeight.get(3) + chloeQortBalanceDeltaAtEachHeight.get(4); + assertEquals(expectedChloeQortBalance, AccountUtils.getBalance(repository, "chloe", Asset.QORT)); + } + } + /** Use Alice-Chloe reward-share to bump Chloe from level 0 to level 1, then check orphaning works as expected. */ @Test public void testLevel1() throws DataException { @@ -295,7 +336,7 @@ public class RewardTests extends Common { * So Dilbert should receive 100% - legacy QORA holder's share. */ - final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare(); + final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(1); final long remainingShare = 1_00000000 - qoraHoldersShare; long dilbertExpectedBalance = initialBalances.get("dilbert").get(Asset.QORT); @@ -702,6 +743,15 @@ public class RewardTests extends Common { AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel7And8Reward); AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel7And8Reward); + // Orphan and ensure balances return to their previous values + BlockUtils.orphanBlocks(repository, 1); + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance); + } } @@ -787,6 +837,592 @@ public class RewardTests extends Common { AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel9And10Reward); AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel9And10Reward); + // Orphan and ensure balances return to their previous values + BlockUtils.orphanBlocks(repository, 1); + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance); + + } + } + + /** Test rewards for level 7 and 8 accounts, when the tier doesn't yet have enough minters in it */ + @Test + public void testLevel7And8RewardsPreActivation() throws DataException, IllegalAccessException { + Common.useSettings("test-settings-v2-reward-levels.json"); + + // Set minAccountsToActivateShareBin to 3 so that share bins 7-8 and 9-10 are considered inactive + FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 3, true); + + try (final Repository repository = RepositoryManager.getRepository()) { + + List cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel(); + List mintingAndOnlineAccounts = new ArrayList<>(); + + // Alice self share online + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + mintingAndOnlineAccounts.add(aliceSelfShare); + + // Bob self-share NOT online + + // Chloe self share online + byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0); + PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey); + mintingAndOnlineAccounts.add(chloeRewardShareAccount); + + // Dilbert self share online + byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0); + PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey); + mintingAndOnlineAccounts.add(dilbertRewardShareAccount); + + // Mint enough blocks to bump testAccount levels to 7 and 8 + final int minterBlocksNeeded = cumulativeBlocksByLevel.get(8) - 20; // 20 blocks before level 8, so that the test accounts reach the correct levels + for (int bc = 0; bc < minterBlocksNeeded; ++bc) + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that the levels are as we expect + assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel()); + assertEquals(7, (int) Common.getTestAccount(repository, "chloe").getLevel()); + assertEquals(8, (int) Common.getTestAccount(repository, "dilbert").getLevel()); + + // Now that everyone is at level 7 or 8 (except Bob who has only just started minting, so is at level 1), we can capture initial balances + Map> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA); + final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT); + final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT); + final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT); + final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT); + + // Mint a block + final long blockReward = BlockUtils.getNextBlockReward(repository); + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure we are using the correct block reward value + assertEquals(100000000L, blockReward); + + /* + * Alice, Chloe, and Dilbert are 'online'. + * Chloe is level 7; Dilbert is level 8. + * One founder online (Alice, who is also level 7). + * No legacy QORA holders. + * + * Level 7 and 8 is not yet activated, so its rewards are added to the level 5 and 6 share bin. + * There are no level 5 and 6 online. + * Chloe and Dilbert should receive equal shares of the 35% block reward for levels 5 to 8. + * Alice should receive the remainder (65%). + */ + + final int level5To8SharePercent = 35_00; // 35% (combined 15% and 20%) + final long level5To8ShareAmount = (blockReward * level5To8SharePercent) / 100L / 100L; + final long expectedLevel5To8Reward = level5To8ShareAmount / 2; // The reward is split between Chloe and Dilbert + final long expectedFounderReward = blockReward - level5To8ShareAmount; // Alice should receive the remainder + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To8Reward); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To8Reward); + + // Orphan and ensure balances return to their previous values + BlockUtils.orphanBlocks(repository, 1); + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance); + + } + } + + /** Test rewards for level 9 and 10 accounts, when the tier doesn't yet have enough minters in it. + * Tier 7-8 isn't activated either, so the rewards and minters are all moved to tier 5-6. */ + @Test + public void testLevel9And10RewardsPreActivation() throws DataException, IllegalAccessException { + Common.useSettings("test-settings-v2-reward-levels.json"); + + // Set minAccountsToActivateShareBin to 3 so that share bins 7-8 and 9-10 are considered inactive + FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 3, true); + + try (final Repository repository = RepositoryManager.getRepository()) { + + List cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel(); + List mintingAndOnlineAccounts = new ArrayList<>(); + + // Alice self share online + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + mintingAndOnlineAccounts.add(aliceSelfShare); + + // Bob self-share not initially online + + // Chloe self share online + byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0); + PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey); + mintingAndOnlineAccounts.add(chloeRewardShareAccount); + + // Dilbert self share online + byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0); + PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey); + mintingAndOnlineAccounts.add(dilbertRewardShareAccount); + + // Mint enough blocks to bump testAccount levels to 9 and 10 + final int minterBlocksNeeded = cumulativeBlocksByLevel.get(10) - 20; // 20 blocks before level 10, so that the test accounts reach the correct levels + for (int bc = 0; bc < minterBlocksNeeded; ++bc) + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Bob self-share now comes online + byte[] bobRewardSharePrivateKey = AccountUtils.rewardShare(repository, "bob", "bob", 0); + PrivateKeyAccount bobRewardShareAccount = new PrivateKeyAccount(repository, bobRewardSharePrivateKey); + mintingAndOnlineAccounts.add(bobRewardShareAccount); + + // Ensure that the levels are as we expect + assertEquals(9, (int) Common.getTestAccount(repository, "alice").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel()); + assertEquals(9, (int) Common.getTestAccount(repository, "chloe").getLevel()); + assertEquals(10, (int) Common.getTestAccount(repository, "dilbert").getLevel()); + + // Now that everyone is at level 7 or 8 (except Bob who has only just started minting, so is at level 1), we can capture initial balances + Map> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA); + final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT); + final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT); + final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT); + final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT); + + // Mint a block + final long blockReward = BlockUtils.getNextBlockReward(repository); + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure we are using the correct block reward value + assertEquals(100000000L, blockReward); + + /* + * Alice, Bob, Chloe, and Dilbert are 'online'. + * Bob is level 1; Chloe is level 9; Dilbert is level 10. + * One founder online (Alice, who is also level 9). + * No legacy QORA holders. + * + * Levels 7+8, and 9+10 are not yet activated, so their rewards are added to the level 5 and 6 share bin. + * There are no levels 5-8 online. + * Chloe and Dilbert should receive equal shares of the 60% block reward for levels 5 to 10. + * Alice should receive the remainder (40%). + */ + + final int level1And2SharePercent = 5_00; // 5% + final int level5To10SharePercent = 60_00; // 60% (combined 15%, 20%, and 25%) + final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L; + final long level5To10ShareAmount = (blockReward * level5To10SharePercent) / 100L / 100L; + final long expectedLevel1And2Reward = level1And2ShareAmount; // The reward is given entirely to Bob + final long expectedLevel5To10Reward = level5To10ShareAmount / 2; // The reward is split between Chloe and Dilbert + final long expectedFounderReward = blockReward - level1And2ShareAmount - level5To10ShareAmount; // Alice should receive the remainder + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance+expectedLevel1And2Reward); + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To10Reward); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To10Reward); + + // Orphan and ensure balances return to their previous values + BlockUtils.orphanBlocks(repository, 1); + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance); + + } + } + + /** Test rewards for level 7 and 8 accounts, when the tier reaches the minimum number of accounts */ + @Test + public void testLevel7And8RewardsPreAndPostActivation() throws DataException, IllegalAccessException { + Common.useSettings("test-settings-v2-reward-levels.json"); + + // Set minAccountsToActivateShareBin to 2 so that share bins 7-8 and 9-10 are considered inactive at first + FieldUtils.writeField(BlockChain.getInstance(), "minAccountsToActivateShareBin", 2, true); + + try (final Repository repository = RepositoryManager.getRepository()) { + + List cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel(); + List mintingAndOnlineAccounts = new ArrayList<>(); + + // Alice self share online + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + mintingAndOnlineAccounts.add(aliceSelfShare); + + // Bob self-share NOT online + + // Chloe self share online + byte[] chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0); + PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey); + mintingAndOnlineAccounts.add(chloeRewardShareAccount); + + // Dilbert self share online + byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0); + PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey); + mintingAndOnlineAccounts.add(dilbertRewardShareAccount); + + // Mint enough blocks to bump two of the testAccount levels to 7 + final int minterBlocksNeeded = cumulativeBlocksByLevel.get(7) - 12; // 12 blocks before level 7, so that dilbert and alice have reached level 7, but chloe will reach it in the next 2 blocks + for (int bc = 0; bc < minterBlocksNeeded; ++bc) + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that the levels are as we expect + assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel()); + assertEquals(6, (int) Common.getTestAccount(repository, "chloe").getLevel()); + assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel()); + + // Now that dilbert has reached level 7, we can capture initial balances + Map> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA); + final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT); + final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT); + final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT); + final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT); + + // Mint a block + long blockReward = BlockUtils.getNextBlockReward(repository); + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure we are using the correct block reward value + assertEquals(100000000L, blockReward); + + /* + * Alice, Chloe, and Dilbert are 'online'. + * Chloe is level 6; Dilbert is level 7. + * One founder online (Alice, who is also level 7). + * No legacy QORA holders. + * + * Level 7 and 8 is not yet activated, so its rewards are added to the level 5 and 6 share bin. + * There are no level 5 and 6 online. + * Chloe and Dilbert should receive equal shares of the 35% block reward for levels 5 to 8. + * Alice should receive the remainder (65%). + */ + + final int level5To8SharePercent = 35_00; // 35% (combined 15% and 20%) + final long level5To8ShareAmount = (blockReward * level5To8SharePercent) / 100L / 100L; + final long expectedLevel5To8Reward = level5To8ShareAmount / 2; // The reward is split between Chloe and Dilbert + final long expectedFounderReward = blockReward - level5To8ShareAmount; // Alice should receive the remainder + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderReward); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel5To8Reward); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel5To8Reward); + + // Ensure that the levels are as we expect + assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel()); + assertEquals(6, (int) Common.getTestAccount(repository, "chloe").getLevel()); + assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel()); + + // Capture pre-activation balances + Map> preActivationBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA); + final long alicePreActivationBalance = preActivationBalances.get("alice").get(Asset.QORT); + final long bobPreActivationBalance = preActivationBalances.get("bob").get(Asset.QORT); + final long chloePreActivationBalance = preActivationBalances.get("chloe").get(Asset.QORT); + final long dilbertPreActivationBalance = preActivationBalances.get("dilbert").get(Asset.QORT); + + // Mint another block + blockReward = BlockUtils.getNextBlockReward(repository); + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that the levels are as we expect (chloe has now increased to level 7; level 7-8 is now activated) + assertEquals(7, (int) Common.getTestAccount(repository, "alice").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel()); + assertEquals(7, (int) Common.getTestAccount(repository, "chloe").getLevel()); + assertEquals(7, (int) Common.getTestAccount(repository, "dilbert").getLevel()); + + /* + * Alice, Chloe, and Dilbert are 'online'. + * Chloe and Dilbert are level 7. + * One founder online (Alice, who is also level 7). + * No legacy QORA holders. + * + * Level 7 and 8 is now activated, so its rewards are paid out in the normal way. + * There are no level 5 and 6 online. + * Chloe and Dilbert should receive equal shares of the 20% block reward for levels 7 to 8. + * Alice should receive the remainder (80%). + */ + + final int level7To8SharePercent = 20_00; // 20% + final long level7To8ShareAmount = (blockReward * level7To8SharePercent) / 100L / 100L; + final long expectedLevel7To8Reward = level7To8ShareAmount / 2; // The reward is split between Chloe and Dilbert + final long newExpectedFounderReward = blockReward - level7To8ShareAmount; // Alice should receive the remainder + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, alicePreActivationBalance+newExpectedFounderReward); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobPreActivationBalance); // Bob not online so his balance remains the same + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloePreActivationBalance+expectedLevel7To8Reward); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertPreActivationBalance+expectedLevel7To8Reward); + + + // Orphan and ensure balances return to their pre-activation values + BlockUtils.orphanBlocks(repository, 1); + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, alicePreActivationBalance); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobPreActivationBalance); + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloePreActivationBalance); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertPreActivationBalance); + + + // Orphan again and ensure balances return to their initial values + BlockUtils.orphanBlocks(repository, 1); + + // Validate the balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance); + + } + } + + /** Test rewards for level 1 and 2 accounts with V2 share-bin layout (post QORA reduction) */ + @Test + public void testLevel1And2RewardsShareBinsV2() throws DataException { + Common.useSettings("test-settings-v2-reward-levels.json"); + + try (final Repository repository = RepositoryManager.getRepository()) { + + List mintingAndOnlineAccounts = new ArrayList<>(); + + // Alice self share online + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + mintingAndOnlineAccounts.add(aliceSelfShare); + byte[] chloeRewardSharePrivateKey; + // Bob self-share NOT online + + // Mint some blocks, to get us close to the V2 activation, but with some room for Chloe and Dilbert to start minting some blocks + for (int i=0; i<990; i++) + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Chloe self share comes online + try { + chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0); + } catch (IllegalArgumentException ex) { + LOGGER.error("FAILED {}", ex.getLocalizedMessage(), ex); + throw ex; + } + PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey); + mintingAndOnlineAccounts.add(chloeRewardShareAccount); + + // Dilbert self share comes online + byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0); + PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey); + mintingAndOnlineAccounts.add(dilbertRewardShareAccount); + + // Mint 6 more blocks, so that V2 share bins are nearly activated + for (int i=0; i<6; i++) + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that the levels are as we expect + assertEquals(10, (int) Common.getTestAccount(repository, "alice").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "chloe").getLevel()); + assertEquals(2, (int) Common.getTestAccount(repository, "dilbert").getLevel()); + + // Ensure that only Alice is a founder + assertEquals(1, getFlags(repository, "alice")); + assertEquals(0, getFlags(repository, "bob")); + assertEquals(0, getFlags(repository, "chloe")); + assertEquals(0, getFlags(repository, "dilbert")); + + // Now that everyone is at level 1 or 2, we can capture initial balances + Map> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA); + final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT); + final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT); + final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT); + final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT); + + // Mint a block + final long blockReward = BlockUtils.getNextBlockReward(repository); + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure we are at the correct height and block reward value + assertEquals(1000, (int) repository.getBlockRepository().getLastBlock().getHeight()); + assertEquals(100000000L, blockReward); + + // We are past the sharesByLevelV2Height feature trigger, so we expect level 1 and 2 to share the increased reward (6%) + final int level1And2SharePercent = 6_00; // 6% + final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L; + final long expectedLevel1And2RewardV2 = level1And2ShareAmount / 2; // The reward is split between Chloe and Dilbert + final long expectedFounderRewardV2 = blockReward - level1And2ShareAmount; // Alice should receive the remainder + + // Validate the balances + assertEquals(6000000, level1And2ShareAmount); + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderRewardV2); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance+expectedLevel1And2RewardV2); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel1And2RewardV2); + + // Now orphan the latest block. This brings us to the threshold of the sharesByLevelV2Height feature trigger + BlockUtils.orphanBlocks(repository, 1); + assertEquals(999, (int) repository.getBlockRepository().getLastBlock().getHeight()); + + // Ensure the latest block rewards have been subtracted and they have returned to their initial values + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance); + + // Orphan another block. This time, the block that was orphaned was prior to the sharesByLevelV2Height feature trigger. + BlockUtils.orphanBlocks(repository, 1); + assertEquals(998, (int) repository.getBlockRepository().getLastBlock().getHeight()); + + // In V1 of share-bins, level 1-2 pays out 5% instead of 6% + final int level1And2SharePercentV1 = 5_00; // 5% + final long level1And2ShareAmountV1 = (blockReward * level1And2SharePercentV1) / 100L / 100L; + final long expectedLevel1And2RewardV1 = level1And2ShareAmountV1 / 2; // The reward is split between Chloe and Dilbert + final long expectedFounderRewardV1 = blockReward - level1And2ShareAmountV1; // Alice should receive the remainder + + // Validate the share amounts and balances + assertEquals(5000000, level1And2ShareAmountV1); + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance-expectedFounderRewardV1); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance-expectedLevel1And2RewardV1); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance-expectedLevel1And2RewardV1); + + // Orphan the latest block one last time + BlockUtils.orphanBlocks(repository, 1); + assertEquals(997, (int) repository.getBlockRepository().getLastBlock().getHeight()); + + // Validate balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance-(expectedFounderRewardV1*2)); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance-(expectedLevel1And2RewardV1*2)); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance-(expectedLevel1And2RewardV1*2)); + + } + } + + /** Test rewards for level 1 and 2 accounts with V2 share-bin layout (post QORA reduction) + * plus some legacy QORA holders */ + @Test + public void testLevel1And2RewardsShareBinsV2WithQoraHolders() throws DataException { + Common.useSettings("test-settings-v2-qora-holder-extremes.json"); + + try (final Repository repository = RepositoryManager.getRepository()) { + + List mintingAndOnlineAccounts = new ArrayList<>(); + + // Some legacy QORA holders exist (Bob and Chloe) + + // Alice self share online + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + mintingAndOnlineAccounts.add(aliceSelfShare); + byte[] chloeRewardSharePrivateKey; + // Bob self-share NOT online + + // Mint some blocks, to get us close to the V2 activation, but with some room for Chloe and Dilbert to start minting some blocks + for (int i=0; i<990; i++) + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Chloe self share comes online + try { + chloeRewardSharePrivateKey = AccountUtils.rewardShare(repository, "chloe", "chloe", 0); + } catch (IllegalArgumentException ex) { + LOGGER.error("FAILED {}", ex.getLocalizedMessage(), ex); + throw ex; + } + PrivateKeyAccount chloeRewardShareAccount = new PrivateKeyAccount(repository, chloeRewardSharePrivateKey); + mintingAndOnlineAccounts.add(chloeRewardShareAccount); + + // Dilbert self share comes online + byte[] dilbertRewardSharePrivateKey = AccountUtils.rewardShare(repository, "dilbert", "dilbert", 0); + PrivateKeyAccount dilbertRewardShareAccount = new PrivateKeyAccount(repository, dilbertRewardSharePrivateKey); + mintingAndOnlineAccounts.add(dilbertRewardShareAccount); + + // Mint 6 more blocks, so that V2 share bins are nearly activated + for (int i=0; i<6; i++) + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that the levels are as we expect + assertEquals(10, (int) Common.getTestAccount(repository, "alice").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "bob").getLevel()); + assertEquals(1, (int) Common.getTestAccount(repository, "chloe").getLevel()); + assertEquals(2, (int) Common.getTestAccount(repository, "dilbert").getLevel()); + + // Ensure that only Alice is a founder + assertEquals(1, getFlags(repository, "alice")); + assertEquals(0, getFlags(repository, "bob")); + assertEquals(0, getFlags(repository, "chloe")); + assertEquals(0, getFlags(repository, "dilbert")); + + // Now that everyone is at level 1 or 2, we can capture initial balances + Map> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA); + final long aliceInitialBalance = initialBalances.get("alice").get(Asset.QORT); + final long bobInitialBalance = initialBalances.get("bob").get(Asset.QORT); + final long chloeInitialBalance = initialBalances.get("chloe").get(Asset.QORT); + final long dilbertInitialBalance = initialBalances.get("dilbert").get(Asset.QORT); + + // Mint a block + final long blockReward = BlockUtils.getNextBlockReward(repository); + BlockMinter.mintTestingBlock(repository, mintingAndOnlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure we are at the correct height and block reward value + assertEquals(1000, (int) repository.getBlockRepository().getLastBlock().getHeight()); + assertEquals(100000000L, blockReward); + + // We are past the sharesByLevelV2Height feature trigger, so we expect level 1 and 2 to share the increased reward (6%) + // and the QORA share will be 1% + final int level1And2SharePercent = 6_00; // 6% + final int qoraSharePercentV2 = 1_00; // 1% + final long qoraShareAmountV2 = (blockReward * qoraSharePercentV2) / 100L / 100L; + final long level1And2ShareAmount = (blockReward * level1And2SharePercent) / 100L / 100L; + final long expectedLevel1And2RewardV2 = level1And2ShareAmount / 2; // The reward is split between Chloe and Dilbert + final long expectedFounderRewardV2 = blockReward - level1And2ShareAmount - qoraShareAmountV2; // Alice should receive the remainder + + // Validate the balances + assertEquals(6000000, level1And2ShareAmount); + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance+expectedFounderRewardV2); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + // Chloe is a QORA holder and will receive additional QORT, so it's not easy to pre-calculate her balance + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance+expectedLevel1And2RewardV2); + + // Now orphan the latest block. This brings us to the threshold of the sharesByLevelV2Height feature trigger + BlockUtils.orphanBlocks(repository, 1); + assertEquals(999, (int) repository.getBlockRepository().getLastBlock().getHeight()); + + // Ensure the latest block rewards have been subtracted and they have returned to their initial values + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + AccountUtils.assertBalance(repository, "chloe", Asset.QORT, chloeInitialBalance); + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance); + + // Orphan another block. This time, the block that was orphaned was prior to the sharesByLevelV2Height feature trigger. + BlockUtils.orphanBlocks(repository, 1); + assertEquals(998, (int) repository.getBlockRepository().getLastBlock().getHeight()); + + // In V1 of share-bins, level 1-2 pays out 5% instead of 6%, and the QORA share is higher at 20% + final int level1And2SharePercentV1 = 5_00; // 5% + final int qoraSharePercentV1 = 20_00; // 20% + final long qoraShareAmountV1 = (blockReward * qoraSharePercentV1) / 100L / 100L; + final long level1And2ShareAmountV1 = (blockReward * level1And2SharePercentV1) / 100L / 100L; + final long expectedLevel1And2RewardV1 = level1And2ShareAmountV1 / 2; // The reward is split between Chloe and Dilbert + final long expectedFounderRewardV1 = blockReward - level1And2ShareAmountV1 - qoraShareAmountV1; // Alice should receive the remainder + + // Validate the share amounts and balances + assertEquals(5000000, level1And2ShareAmountV1); + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance-expectedFounderRewardV1); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + // Chloe is a QORA holder and will receive additional QORT, so it's not easy to pre-calculate her balance + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance-expectedLevel1And2RewardV1); + + // Orphan the latest block one last time + BlockUtils.orphanBlocks(repository, 1); + assertEquals(997, (int) repository.getBlockRepository().getLastBlock().getHeight()); + + // Validate balances + AccountUtils.assertBalance(repository, "alice", Asset.QORT, aliceInitialBalance-(expectedFounderRewardV1*2)); + AccountUtils.assertBalance(repository, "bob", Asset.QORT, bobInitialBalance); // Bob not online so his balance remains the same + // Chloe is a QORA holder and will receive additional QORT, so it's not easy to pre-calculate her balance + AccountUtils.assertBalance(repository, "dilbert", Asset.QORT, dilbertInitialBalance-(expectedLevel1And2RewardV1*2)); + } } diff --git a/src/test/java/org/qortal/test/naming/BuySellTests.java b/src/test/java/org/qortal/test/naming/BuySellTests.java index faed3d72..4530820e 100644 --- a/src/test/java/org/qortal/test/naming/BuySellTests.java +++ b/src/test/java/org/qortal/test/naming/BuySellTests.java @@ -165,6 +165,52 @@ public class BuySellTests extends Common { assertEquals("price incorrect", price, nameData.getSalePrice()); } + @Test + public void testCancelSellNameAndRelist() throws DataException { + // Register-name and sell-name + testSellName(); + + // Cancel Sell-name + CancelSellNameTransactionData transactionData = new CancelSellNameTransactionData(TestTransaction.generateBase(alice), name); + TransactionUtils.signAndMint(repository, transactionData, alice); + + NameData nameData; + + // Check name is no longer for sale + nameData = repository.getNameRepository().fromName(name); + assertFalse(nameData.isForSale()); + assertNull(nameData.getSalePrice()); + + // Re-sell-name + Long newPrice = random.nextInt(1000) * Amounts.MULTIPLIER; + SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(TestTransaction.generateBase(alice), name, newPrice); + TransactionUtils.signAndMint(repository, sellNameTransactionData, alice); + + // Check name is for sale + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.isForSale()); + assertEquals("price incorrect", newPrice, nameData.getSalePrice()); + + // Orphan sell-name + BlockUtils.orphanLastBlock(repository); + + // Check name no longer for sale + nameData = repository.getNameRepository().fromName(name); + assertFalse(nameData.isForSale()); + assertNull(nameData.getSalePrice()); + + // Orphan cancel-sell-name + BlockUtils.orphanLastBlock(repository); + + // Check name is for sale (at original price) + nameData = repository.getNameRepository().fromName(name); + assertTrue(nameData.isForSale()); + assertEquals("price incorrect", price, nameData.getSalePrice()); + + // Orphan sell-name and register-name + BlockUtils.orphanBlocks(repository, 2); + } + @Test public void testBuyName() throws DataException { // Register-name and sell-name diff --git a/src/test/java/org/qortal/test/network/OnlineAccountsTests.java b/src/test/java/org/qortal/test/network/OnlineAccountsTests.java index 4154121c..c9e646f1 100644 --- a/src/test/java/org/qortal/test/network/OnlineAccountsTests.java +++ b/src/test/java/org/qortal/test/network/OnlineAccountsTests.java @@ -1,22 +1,39 @@ package org.qortal.test.network; +import com.google.common.primitives.Ints; +import io.druid.extendedset.intset.ConciseSet; +import org.apache.commons.lang3.reflect.FieldUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; +import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; +import org.qortal.block.Block; +import org.qortal.block.BlockChain; +import org.qortal.controller.BlockMinter; import org.qortal.data.network.OnlineAccountData; import org.qortal.network.message.*; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; +import org.qortal.test.common.AccountUtils; +import org.qortal.test.common.Common; import org.qortal.transform.Transformer; +import org.qortal.utils.Base58; +import org.qortal.utils.NTP; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.security.Security; import java.util.ArrayList; import java.util.List; import java.util.Random; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; -public class OnlineAccountsTests { +public class OnlineAccountsTests extends Common { private static final Random RANDOM = new Random(); static { @@ -27,6 +44,12 @@ public class OnlineAccountsTests { Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); } + @Before + public void beforeTest() throws DataException, IOException { + Common.useSettingsAndDb("test-settings-v2.json", false); + NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset()); + } + @Test public void testGetOnlineAccountsV2() throws MessageException { @@ -111,4 +134,66 @@ public class OnlineAccountsTests { return onlineAccounts; } + @Test + public void testOnlineAccountsModulusV1() throws IllegalAccessException, DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Set feature trigger timestamp to MAX long so that it is inactive + FieldUtils.writeField(BlockChain.getInstance(), "onlineAccountsModulusV2Timestamp", Long.MAX_VALUE, true); + + List onlineAccountSignatures = new ArrayList<>(); + long fakeNTPOffset = 0L; + + // Mint a block and store its timestamp + Block block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share")); + long lastBlockTimestamp = block.getBlockData().getTimestamp(); + + // Mint some blocks and keep track of the different online account signatures + for (int i = 0; i < 30; i++) { + block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share")); + + // Increase NTP fixed offset by the block time, to simulate time passing + long blockTimeDelta = block.getBlockData().getTimestamp() - lastBlockTimestamp; + lastBlockTimestamp = block.getBlockData().getTimestamp(); + fakeNTPOffset += blockTimeDelta; + NTP.setFixedOffset(fakeNTPOffset); + + String lastOnlineAccountSignatures58 = Base58.encode(block.getBlockData().getOnlineAccountsSignatures()); + if (!onlineAccountSignatures.contains(lastOnlineAccountSignatures58)) { + onlineAccountSignatures.add(lastOnlineAccountSignatures58); + } + } + + // We expect at least 6 unique signatures over 30 blocks (generally 6-8, but could be higher due to block time differences) + System.out.println(String.format("onlineAccountSignatures count: %d", onlineAccountSignatures.size())); + assertTrue(onlineAccountSignatures.size() >= 6); + } + } + + @Test + @Ignore(value = "For informational use") + public void testOnlineAccountNonceCompression() throws IOException { + List onlineAccounts = AccountUtils.generateOnlineAccounts(5000); + + // Build array of nonce values + List accountNonces = new ArrayList<>(); + for (OnlineAccountData onlineAccountData : onlineAccounts) { + accountNonces.add(onlineAccountData.getNonce()); + } + + // Write nonces into ConciseSet + ConciseSet nonceSet = new ConciseSet(); + nonceSet = nonceSet.convert(accountNonces); + byte[] conciseEncodedNonces = nonceSet.toByteBuffer().array(); + + // Also write to regular byte array of ints, for comparison + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + for (Integer nonce : accountNonces) { + bytes.write(Ints.toByteArray(nonce)); + } + byte[] standardEncodedNonces = bytes.toByteArray(); + + System.out.println(String.format("Standard: %d", standardEncodedNonces.length)); + System.out.println(String.format("Concise: %d", conciseEncodedNonces.length)); + } } diff --git a/src/test/java/org/qortal/test/network/OnlineAccountsV3Tests.java b/src/test/java/org/qortal/test/network/OnlineAccountsV3Tests.java new file mode 100644 index 00000000..6136c1e1 --- /dev/null +++ b/src/test/java/org/qortal/test/network/OnlineAccountsV3Tests.java @@ -0,0 +1,210 @@ +package org.qortal.test.network; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; +import org.junit.Ignore; +import org.junit.Test; +import org.qortal.controller.OnlineAccountsManager; +import org.qortal.data.network.OnlineAccountData; +import org.qortal.network.message.*; +import org.qortal.transform.Transformer; + +import java.nio.ByteBuffer; +import java.security.Security; +import java.util.*; + +import static org.junit.Assert.*; + +public class OnlineAccountsV3Tests { + + private static final Random RANDOM = new Random(); + static { + // This must go before any calls to LogManager/Logger + System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); + } + + @Ignore("For informational use") + @Test + public void compareV2ToV3() throws MessageException { + List onlineAccounts = generateOnlineAccounts(false); + + // How many of each timestamp and leading byte (of public key) + Map> hashesByTimestampThenByte = convertToHashMaps(onlineAccounts); + + byte[] v3DataBytes = new GetOnlineAccountsV3Message(hashesByTimestampThenByte).toBytes(); + int v3ByteSize = v3DataBytes.length; + + byte[] v2DataBytes = new GetOnlineAccountsV2Message(onlineAccounts).toBytes(); + int v2ByteSize = v2DataBytes.length; + + int numTimestamps = hashesByTimestampThenByte.size(); + System.out.printf("For %d accounts split across %d timestamp%s: V2 size %d vs V3 size %d%n", + onlineAccounts.size(), + numTimestamps, + numTimestamps != 1 ? "s" : "", + v2ByteSize, + v3ByteSize + ); + + for (var outerMapEntry : hashesByTimestampThenByte.entrySet()) { + long timestamp = outerMapEntry.getKey(); + + var innerMap = outerMapEntry.getValue(); + + System.out.printf("For timestamp %d: %d / 256 slots used.%n", + timestamp, + innerMap.size() + ); + } + } + + private Map> convertToHashMaps(List onlineAccounts) { + // How many of each timestamp and leading byte (of public key) + Map> hashesByTimestampThenByte = new HashMap<>(); + + for (OnlineAccountData onlineAccountData : onlineAccounts) { + Long timestamp = onlineAccountData.getTimestamp(); + Byte leadingByte = onlineAccountData.getPublicKey()[0]; + + hashesByTimestampThenByte + .computeIfAbsent(timestamp, k -> new HashMap<>()) + .compute(leadingByte, (k, v) -> OnlineAccountsManager.xorByteArrayInPlace(v, onlineAccountData.getPublicKey())); + } + + return hashesByTimestampThenByte; + } + + @Test + public void testOnGetOnlineAccountsV3() { + List ourOnlineAccounts = generateOnlineAccounts(false); + List peersOnlineAccounts = generateOnlineAccounts(false); + + Map> ourConvertedHashes = convertToHashMaps(ourOnlineAccounts); + Map> peersConvertedHashes = convertToHashMaps(peersOnlineAccounts); + + List mockReply = new ArrayList<>(); + + // Warning: no double-checking/fetching - we must be ConcurrentMap compatible! + // So no contains()-then-get() or multiple get()s on the same key/map. + for (var ourOuterMapEntry : ourConvertedHashes.entrySet()) { + Long timestamp = ourOuterMapEntry.getKey(); + + var ourInnerMap = ourOuterMapEntry.getValue(); + var peersInnerMap = peersConvertedHashes.get(timestamp); + + if (peersInnerMap == null) { + // Peer doesn't have this timestamp, so if it's valid (i.e. not too old) then we'd have to send all of ours + for (Byte leadingByte : ourInnerMap.keySet()) + mockReply.add(timestamp + ":" + leadingByte); + } else { + // We have entries for this timestamp so compare against peer's entries + for (var ourInnerMapEntry : ourInnerMap.entrySet()) { + Byte leadingByte = ourInnerMapEntry.getKey(); + byte[] peersHash = peersInnerMap.get(leadingByte); + + if (!Arrays.equals(ourInnerMapEntry.getValue(), peersHash)) { + // We don't match peer, or peer doesn't have - send all online accounts for this timestamp and leading byte + mockReply.add(timestamp + ":" + leadingByte); + } + } + } + } + + int numOurTimestamps = ourConvertedHashes.size(); + System.out.printf("We have %d accounts split across %d timestamp%s%n", + ourOnlineAccounts.size(), + numOurTimestamps, + numOurTimestamps != 1 ? "s" : "" + ); + + int numPeerTimestamps = peersConvertedHashes.size(); + System.out.printf("Peer sent %d accounts split across %d timestamp%s%n", + peersOnlineAccounts.size(), + numPeerTimestamps, + numPeerTimestamps != 1 ? "s" : "" + ); + + System.out.printf("We need to send: %d%n%s%n", mockReply.size(), String.join(", ", mockReply)); + } + + @Test + public void testSerialization() throws MessageException { + List onlineAccountsOut = generateOnlineAccounts(true); + Map> hashesByTimestampThenByteOut = convertToHashMaps(onlineAccountsOut); + + validateSerialization(hashesByTimestampThenByteOut); + } + + @Test + public void testEmptySerialization() throws MessageException { + Map> hashesByTimestampThenByteOut = Collections.emptyMap(); + validateSerialization(hashesByTimestampThenByteOut); + + hashesByTimestampThenByteOut = new HashMap<>(); + validateSerialization(hashesByTimestampThenByteOut); + } + + private void validateSerialization(Map> hashesByTimestampThenByteOut) throws MessageException { + Message messageOut = new GetOnlineAccountsV3Message(hashesByTimestampThenByteOut); + byte[] messageBytes = messageOut.toBytes(); + + ByteBuffer byteBuffer = ByteBuffer.wrap(messageBytes).asReadOnlyBuffer(); + + GetOnlineAccountsV3Message messageIn = (GetOnlineAccountsV3Message) Message.fromByteBuffer(byteBuffer); + + Map> hashesByTimestampThenByteIn = messageIn.getHashesByTimestampThenByte(); + + Set timestampsIn = hashesByTimestampThenByteIn.keySet(); + Set timestampsOut = hashesByTimestampThenByteOut.keySet(); + assertEquals("timestamp count mismatch", timestampsOut.size(), timestampsIn.size()); + assertTrue("timestamps mismatch", timestampsIn.containsAll(timestampsOut)); + + for (Long timestamp : timestampsIn) { + Map hashesByByteIn = hashesByTimestampThenByteIn.get(timestamp); + Map hashesByByteOut = hashesByTimestampThenByteOut.get(timestamp); + assertNotNull("timestamp entry missing", hashesByByteOut); + + Set leadingBytesIn = hashesByByteIn.keySet(); + Set leadingBytesOut = hashesByByteOut.keySet(); + assertEquals("leading byte entry count mismatch", leadingBytesOut.size(), leadingBytesIn.size()); + assertTrue("leading byte entry mismatch", leadingBytesIn.containsAll(leadingBytesOut)); + + for (Byte leadingByte : leadingBytesOut) { + byte[] bytesIn = hashesByByteIn.get(leadingByte); + byte[] bytesOut = hashesByByteOut.get(leadingByte); + + assertTrue("pubkey hash mismatch", Arrays.equals(bytesOut, bytesIn)); + } + } + } + + private List generateOnlineAccounts(boolean withSignatures) { + List onlineAccounts = new ArrayList<>(); + + int numTimestamps = RANDOM.nextInt(2) + 1; // 1 or 2 + + for (int t = 0; t < numTimestamps; ++t) { + long timestamp = 1 << 31 + (t + 1) << 12; + int numAccounts = RANDOM.nextInt(3000); + + for (int a = 0; a < numAccounts; ++a) { + byte[] sig = null; + if (withSignatures) { + sig = new byte[Transformer.SIGNATURE_LENGTH]; + RANDOM.nextBytes(sig); + } + + byte[] pubkey = new byte[Transformer.PUBLIC_KEY_LENGTH]; + RANDOM.nextBytes(pubkey); + + onlineAccounts.add(new OnlineAccountData(timestamp, sig, pubkey)); + } + } + + return onlineAccounts; + } + +} diff --git a/src/test/java/org/qortal/test/at/AtSerializationTests.java b/src/test/java/org/qortal/test/serialization/AtSerializationTests.java similarity index 99% rename from src/test/java/org/qortal/test/at/AtSerializationTests.java rename to src/test/java/org/qortal/test/serialization/AtSerializationTests.java index 3953bcdf..ea8d6bcd 100644 --- a/src/test/java/org/qortal/test/at/AtSerializationTests.java +++ b/src/test/java/org/qortal/test/serialization/AtSerializationTests.java @@ -1,4 +1,4 @@ -package org.qortal.test.at; +package org.qortal.test.serialization; import com.google.common.hash.HashCode; import org.junit.After; diff --git a/src/test/java/org/qortal/test/serialization/ChatSerializationTests.java b/src/test/java/org/qortal/test/serialization/ChatSerializationTests.java new file mode 100644 index 00000000..983896db --- /dev/null +++ b/src/test/java/org/qortal/test/serialization/ChatSerializationTests.java @@ -0,0 +1,102 @@ +package org.qortal.test.serialization; + +import com.google.common.hash.HashCode; +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.data.transaction.ChatTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.Common; +import org.qortal.test.common.transaction.ChatTestTransaction; +import org.qortal.transaction.Transaction; +import org.qortal.transform.TransformationException; +import org.qortal.transform.transaction.TransactionTransformer; +import org.qortal.utils.Base58; + +import static org.junit.Assert.*; + +public class ChatSerializationTests { + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + + @Test + public void testChatSerializationWithChatReference() throws DataException, TransformationException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Build MESSAGE-type AT transaction with chatReference + PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice"); + ChatTransactionData transactionData = (ChatTransactionData) ChatTestTransaction.randomTransaction(repository, signingAccount, true); + Transaction transaction = Transaction.fromData(repository, transactionData); + transaction.sign(signingAccount); + + assertNotNull(transactionData.getChatReference()); + + final int claimedLength = TransactionTransformer.getDataLength(transactionData); + byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData); + assertEquals("Serialized CHAT transaction length differs from declared length", claimedLength, serializedTransaction.length); + + TransactionData deserializedTransactionData = TransactionTransformer.fromBytes(serializedTransaction); + // Re-sign + Transaction deserializedTransaction = Transaction.fromData(repository, deserializedTransactionData); + deserializedTransaction.sign(signingAccount); + assertEquals("Deserialized CHAT transaction signature differs", Base58.encode(transactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature())); + + // Re-serialize to check new length and bytes + final int reclaimedLength = TransactionTransformer.getDataLength(deserializedTransactionData); + assertEquals("Reserialized CHAT transaction declared length differs", claimedLength, reclaimedLength); + + byte[] reserializedTransaction = TransactionTransformer.toBytes(deserializedTransactionData); + assertEquals("Reserialized CHAT transaction bytes differ", HashCode.fromBytes(serializedTransaction).toString(), HashCode.fromBytes(reserializedTransaction).toString()); + + // Deserialized chat reference must match initial chat reference + ChatTransactionData deserializedChatTransactionData = (ChatTransactionData) deserializedTransactionData; + assertNotNull(deserializedChatTransactionData.getChatReference()); + assertArrayEquals(deserializedChatTransactionData.getChatReference(), transactionData.getChatReference()); + } + } + + @Test + public void testChatSerializationWithoutChatReference() throws DataException, TransformationException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Build MESSAGE-type AT transaction without chatReference + PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice"); + ChatTransactionData transactionData = (ChatTransactionData) ChatTestTransaction.randomTransaction(repository, signingAccount, true); + transactionData.setChatReference(null); + Transaction transaction = Transaction.fromData(repository, transactionData); + transaction.sign(signingAccount); + + assertNull(transactionData.getChatReference()); + + final int claimedLength = TransactionTransformer.getDataLength(transactionData); + byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData); + assertEquals("Serialized CHAT transaction length differs from declared length", claimedLength, serializedTransaction.length); + + TransactionData deserializedTransactionData = TransactionTransformer.fromBytes(serializedTransaction); + // Re-sign + Transaction deserializedTransaction = Transaction.fromData(repository, deserializedTransactionData); + deserializedTransaction.sign(signingAccount); + assertEquals("Deserialized CHAT transaction signature differs", Base58.encode(transactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature())); + + // Re-serialize to check new length and bytes + final int reclaimedLength = TransactionTransformer.getDataLength(deserializedTransactionData); + assertEquals("Reserialized CHAT transaction declared length differs", claimedLength, reclaimedLength); + + byte[] reserializedTransaction = TransactionTransformer.toBytes(deserializedTransactionData); + assertEquals("Reserialized CHAT transaction bytes differ", HashCode.fromBytes(serializedTransaction).toString(), HashCode.fromBytes(reserializedTransaction).toString()); + + // Deserialized chat reference must match initial chat reference + ChatTransactionData deserializedChatTransactionData = (ChatTransactionData) deserializedTransactionData; + assertNull(deserializedChatTransactionData.getChatReference()); + assertArrayEquals(deserializedChatTransactionData.getChatReference(), transactionData.getChatReference()); + } + } + +} diff --git a/src/test/java/org/qortal/test/SerializationTests.java b/src/test/java/org/qortal/test/serialization/SerializationTests.java similarity index 98% rename from src/test/java/org/qortal/test/SerializationTests.java rename to src/test/java/org/qortal/test/serialization/SerializationTests.java index d9fe978c..e9767909 100644 --- a/src/test/java/org/qortal/test/SerializationTests.java +++ b/src/test/java/org/qortal/test/serialization/SerializationTests.java @@ -1,4 +1,4 @@ -package org.qortal.test; +package org.qortal.test.serialization; import org.junit.Ignore; import org.junit.Test; @@ -47,7 +47,6 @@ public class SerializationTests extends Common { switch (txType) { case GENESIS: case ACCOUNT_FLAGS: - case CHAT: case PUBLICIZE: case AIRDROP: case ENABLE_FORGING: @@ -60,6 +59,7 @@ public class SerializationTests extends Common { TransactionData transactionData = TransactionUtils.randomTransaction(repository, signingAccount, txType, true); Transaction transaction = Transaction.fromData(repository, transactionData); transaction.sign(signingAccount); + transaction.importAsUnconfirmed(); final int claimedLength = TransactionTransformer.getDataLength(transactionData); byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData); diff --git a/src/test/resources/test-chain-v2-block-timestamps.json b/src/test/resources/test-chain-v2-block-timestamps.json new file mode 100644 index 00000000..8c2e0503 --- /dev/null +++ b/src/test/resources/test-chain-v2-block-timestamps.json @@ -0,0 +1,105 @@ +{ + "isTestChain": true, + "blockTimestampMargin": 500, + "transactionExpiryPeriod": 86400000, + "maxBlockSize": 2097152, + "maxBytesPerUnitFee": 1024, + "unitFee": "0.1", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], + "requireGroupForApproval": false, + "minAccountLevelToRewardShare": 5, + "maxRewardSharesPerMintingAccount": 20, + "founderEffectiveMintingLevel": 10, + "onlineAccountSignaturesMinLifetime": 3600000, + "onlineAccountSignaturesMaxLifetime": 86400000, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "rewardsByHeight": [ + { "height": 1, "reward": 100 }, + { "height": 11, "reward": 10 }, + { "height": 21, "reward": 1 } + ], + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], + "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, + "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], + "blockTimingsByHeight": [ + { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }, + { "height": 2, "target": 70000, "deviation": 10000, "power": 0.8 } + ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, + "featureTriggers": { + "messageHeight": 0, + "atHeight": 0, + "assetsTimestamp": 0, + "votingTimestamp": 0, + "arbitraryTimestamp": 0, + "powfixTimestamp": 0, + "qortalTimestamp": 0, + "newAssetPricingTimestamp": 0, + "groupApprovalTimestamp": 0, + "atFindNextTransactionFix": 0, + "newBlockSigHeight": 999999, + "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, + "calcChainWeightTimestamp": 0, + "transactionV5Timestamp": 0, + "transactionV6Timestamp": 9999999999999, + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 + }, + "genesisInfo": { + "version": 4, + "timestamp": 0, + "transactions": [ + { "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + { "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + + { "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" }, + { "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" }, + + { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, + + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + + { "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }, + { "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" }, + + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 } + ] + } +} diff --git a/src/test/resources/test-chain-v2-disable-reference.json b/src/test/resources/test-chain-v2-disable-reference.json index f5bf63ac..f7f8e7d8 100644 --- a/src/test/resources/test-chain-v2-disable-reference.json +++ b/src/test/resources/test-chain-v2-disable-reference.json @@ -10,24 +10,41 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -51,10 +68,17 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 0 + "disableReferenceTimestamp": 0, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index 36a3423d..20d10233 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -10,24 +10,42 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -51,10 +69,17 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index 1ff9edea..e71ebab6 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -10,24 +10,42 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -51,10 +69,17 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index d2c522ea..2a388e1f 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -10,24 +10,42 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -51,10 +69,17 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder-extremes.json b/src/test/resources/test-chain-v2-qora-holder-extremes.json index 73593ba5..cface0e7 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -10,24 +10,42 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -50,11 +68,18 @@ "groupApprovalTimestamp": 0, "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, - "shareBinFix": 999999, + "shareBinFix": 0, + "sharesByLevelV2Height": 1000, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, @@ -81,7 +106,10 @@ { "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }, { "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 }, - { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 8 } + { "type": "ACCOUNT_LEVEL", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "level": 1 }, + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 2 }, + { "type": "ACCOUNT_LEVEL", "target": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "level": 1 }, + { "type": "ACCOUNT_LEVEL", "target": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "level": 1 } ] } } diff --git a/src/test/resources/test-chain-v2-qora-holder-reduction.json b/src/test/resources/test-chain-v2-qora-holder-reduction.json new file mode 100644 index 00000000..f233680b --- /dev/null +++ b/src/test/resources/test-chain-v2-qora-holder-reduction.json @@ -0,0 +1,113 @@ +{ + "isTestChain": true, + "blockTimestampMargin": 500, + "transactionExpiryPeriod": 86400000, + "maxBlockSize": 2097152, + "maxBytesPerUnitFee": 1024, + "unitFee": "0.1", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], + "requireGroupForApproval": false, + "minAccountLevelToRewardShare": 5, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], + "founderEffectiveMintingLevel": 10, + "onlineAccountSignaturesMinLifetime": 3600000, + "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "rewardsByHeight": [ + { "height": 1, "reward": 100 }, + { "height": 11, "reward": 10 }, + { "height": 21, "reward": 1 } + ], + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 5, "share": 0.01 } + ], + "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, + "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], + "blockTimingsByHeight": [ + { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } + ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, + "featureTriggers": { + "messageHeight": 0, + "atHeight": 0, + "assetsTimestamp": 0, + "votingTimestamp": 0, + "arbitraryTimestamp": 0, + "powfixTimestamp": 0, + "qortalTimestamp": 0, + "newAssetPricingTimestamp": 0, + "groupApprovalTimestamp": 0, + "atFindNextTransactionFix": 0, + "newBlockSigHeight": 999999, + "shareBinFix": 999999, + "sharesByLevelV2Height": 5, + "rewardShareLimitTimestamp": 9999999999999, + "calcChainWeightTimestamp": 0, + "transactionV5Timestamp": 0, + "transactionV6Timestamp": 0, + "disableReferenceTimestamp": 9999999999999, + "aggregateSignatureTimestamp": 0, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 + }, + "genesisInfo": { + "version": 4, + "timestamp": 0, + "transactions": [ + { "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + { "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + + { "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" }, + { "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" }, + + { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "637557960.49687541", "assetId": 1 }, + { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "0.666", "assetId": 1 }, + + { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, + + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": 1000000, "isDivisible": true, "fee": 0 }, + + { "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }, + { "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 }, + + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 8 } + ] + } +} diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 4bee8c93..4ea82290 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -10,24 +10,42 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -51,10 +69,17 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 999999, + "sharesByLevelV2Height": 1000, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index eba77061..5de8d9ff 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -10,24 +10,42 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 20 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 1, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -51,10 +69,17 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 6, + "sharesByLevelV2Height": 1000, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index 14772c60..c008ed42 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -10,24 +10,42 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 20 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -51,10 +69,17 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-shares.json b/src/test/resources/test-chain-v2-reward-shares.json new file mode 100644 index 00000000..2fc0151f --- /dev/null +++ b/src/test/resources/test-chain-v2-reward-shares.json @@ -0,0 +1,109 @@ +{ + "isTestChain": true, + "blockTimestampMargin": 500, + "transactionExpiryPeriod": 86400000, + "maxBlockSize": 2097152, + "maxBytesPerUnitFee": 1024, + "unitFee": "0.1", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], + "requireGroupForApproval": false, + "minAccountLevelToRewardShare": 5, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 1655460000000, "maxShares": 3 } + ], + "founderEffectiveMintingLevel": 10, + "onlineAccountSignaturesMinLifetime": 3600000, + "onlineAccountSignaturesMaxLifetime": 86400000, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "rewardsByHeight": [ + { "height": 1, "reward": 100 }, + { "height": 11, "reward": 10 }, + { "height": 21, "reward": 1 } + ], + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], + "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, + "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], + "blockTimingsByHeight": [ + { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } + ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, + "featureTriggers": { + "messageHeight": 0, + "atHeight": 0, + "assetsTimestamp": 0, + "votingTimestamp": 0, + "arbitraryTimestamp": 0, + "powfixTimestamp": 0, + "qortalTimestamp": 0, + "newAssetPricingTimestamp": 0, + "groupApprovalTimestamp": 0, + "atFindNextTransactionFix": 0, + "newBlockSigHeight": 999999, + "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 0, + "calcChainWeightTimestamp": 0, + "newConsensusTimestamp": 0, + "transactionV5Timestamp": 0, + "transactionV6Timestamp": 0, + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 + }, + "genesisInfo": { + "version": 4, + "timestamp": 0, + "transactions": [ + { "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + { "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + + { "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" }, + { "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" }, + + { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, + + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + + { "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }, + { "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" }, + + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 } + ] + } +} diff --git a/src/test/resources/test-chain-v2-self-sponsorship-algo.json b/src/test/resources/test-chain-v2-self-sponsorship-algo.json new file mode 100644 index 00000000..68b33cc3 --- /dev/null +++ b/src/test/resources/test-chain-v2-self-sponsorship-algo.json @@ -0,0 +1,116 @@ +{ + "isTestChain": true, + "blockTimestampMargin": 500, + "transactionExpiryPeriod": 86400000, + "maxBlockSize": 2097152, + "maxBytesPerUnitFee": 0, + "unitFee": "0.00000001", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], + "requireGroupForApproval": false, + "minAccountLevelToRewardShare": 5, + "maxRewardSharesPerFounderMintingAccount": 20, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 20 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], + "founderEffectiveMintingLevel": 10, + "onlineAccountSignaturesMinLifetime": 3600000, + "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "rewardsByHeight": [ + { "height": 1, "reward": 100 }, + { "height": 11, "reward": 10 }, + { "height": 21, "reward": 1 } + ], + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], + "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 0, + "shareBinActivationMinLevel": 7, + "blocksNeededByLevel": [ 5, 20, 30, 40, 50, 60, 18, 80, 90, 100 ], + "blockTimingsByHeight": [ + { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } + ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, + "featureTriggers": { + "messageHeight": 0, + "atHeight": 0, + "assetsTimestamp": 0, + "votingTimestamp": 0, + "arbitraryTimestamp": 0, + "powfixTimestamp": 0, + "qortalTimestamp": 0, + "newAssetPricingTimestamp": 0, + "groupApprovalTimestamp": 0, + "atFindNextTransactionFix": 0, + "newBlockSigHeight": 999999, + "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, + "calcChainWeightTimestamp": 0, + "transactionV5Timestamp": 0, + "transactionV6Timestamp": 0, + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 20, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 + }, + "genesisInfo": { + "version": 4, + "timestamp": 0, + "transactions": [ + { "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + { "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + + { "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" }, + { "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" }, + + { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, + + { "type": "UPDATE_GROUP", "ownerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupId": 1, "newOwner": "QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG", "newDescription": "developer group", "newIsOpen": false, "newApprovalThreshold": "PCT40", "minimumBlockDelay": 10, "maximumBlockDelay": 1440 }, + + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + + { "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }, + { "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" }, + + { "type": "ACCOUNT_LEVEL", "target": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "level": 5 }, + { "type": "REWARD_SHARE", "minterPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "rewardSharePublicKey": "CcABzvk26TFEHG7Yok84jxyd4oBtLkx8RJdGFVz2csvp", "sharePercent": 100 }, + + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 }, + { "type": "ACCOUNT_LEVEL", "target": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "level": 5 }, + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 6 } + ] + } +} diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index f897e38d..63abc695 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -10,24 +10,42 @@ ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, - "maxRewardSharesPerMintingAccount": 20, + "maxRewardSharesPerFounderMintingAccount": 6, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 6 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, { "height": 21, "reward": 1 } ], - "sharesByLevel": [ - { "levels": [ 1, 2 ], "share": 0.05 }, - { "levels": [ 3, 4 ], "share": 0.10 }, - { "levels": [ 5, 6 ], "share": 0.15 }, - { "levels": [ 7, 8 ], "share": 0.20 }, - { "levels": [ 9, 10 ], "share": 0.25 } + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } ], - "qoraHoldersShare": 0.20, "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 30, + "shareBinActivationMinLevel": 7, "blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } @@ -51,10 +69,17 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, @@ -71,6 +96,8 @@ { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, + { "type": "UPDATE_GROUP", "ownerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupId": 1, "newOwner": "QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG", "newDescription": "developer group", "newIsOpen": false, "newApprovalThreshold": "PCT40", "minimumBlockDelay": 10, "maximumBlockDelay": 1440 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, { "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, diff --git a/src/test/resources/test-settings-v2-block-timestamps.json b/src/test/resources/test-settings-v2-block-timestamps.json new file mode 100644 index 00000000..dbbbebbe --- /dev/null +++ b/src/test/resources/test-settings-v2-block-timestamps.json @@ -0,0 +1,19 @@ +{ + "repositoryPath": "testdb", + "bitcoinNet": "TEST3", + "litecoinNet": "TEST3", + "restrictedApi": false, + "blockchainConfig": "src/test/resources/test-chain-v2-block-timestamps.json", + "exportPath": "qortal-backup-test", + "bootstrap": false, + "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, + "minPeers": 0, + "pruneBlockLimit": 100, + "bootstrapFilenamePrefix": "test-", + "dataPath": "data-test", + "tempDataPath": "data-test/_temp", + "listsPath": "lists-test", + "storagePolicy": "FOLLOWED_OR_VIEWED", + "maxStorageCapacity": 104857600 +} diff --git a/src/test/resources/test-settings-v2-qora-holder-reduction.json b/src/test/resources/test-settings-v2-qora-holder-reduction.json new file mode 100644 index 00000000..a489cc12 --- /dev/null +++ b/src/test/resources/test-settings-v2-qora-holder-reduction.json @@ -0,0 +1,11 @@ +{ + "repositoryPath": "testdb", + "restrictedApi": false, + "blockchainConfig": "src/test/resources/test-chain-v2-qora-holder-reduction.json", + "exportPath": "qortal-backup-test", + "bootstrap": false, + "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, + "minPeers": 0, + "pruneBlockLimit": 100 +} diff --git a/src/test/resources/test-settings-v2-reward-shares.json b/src/test/resources/test-settings-v2-reward-shares.json new file mode 100644 index 00000000..092ebcc8 --- /dev/null +++ b/src/test/resources/test-settings-v2-reward-shares.json @@ -0,0 +1,19 @@ +{ + "repositoryPath": "testdb", + "bitcoinNet": "TEST3", + "litecoinNet": "TEST3", + "restrictedApi": false, + "blockchainConfig": "src/test/resources/test-chain-v2-reward-shares.json", + "exportPath": "qortal-backup-test", + "bootstrap": false, + "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, + "minPeers": 0, + "pruneBlockLimit": 100, + "bootstrapFilenamePrefix": "test-", + "dataPath": "data-test", + "tempDataPath": "data-test/_temp", + "listsPath": "lists-test", + "storagePolicy": "FOLLOWED_OR_VIEWED", + "maxStorageCapacity": 104857600 +} diff --git a/src/test/resources/test-settings-v2-self-sponsorship-algo.json b/src/test/resources/test-settings-v2-self-sponsorship-algo.json new file mode 100644 index 00000000..5ea42e66 --- /dev/null +++ b/src/test/resources/test-settings-v2-self-sponsorship-algo.json @@ -0,0 +1,20 @@ +{ + "repositoryPath": "testdb", + "bitcoinNet": "TEST3", + "litecoinNet": "TEST3", + "restrictedApi": false, + "blockchainConfig": "src/test/resources/test-chain-v2-self-sponsorship-algo.json", + "exportPath": "qortal-backup-test", + "bootstrap": false, + "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, + "minPeers": 0, + "pruneBlockLimit": 100, + "bootstrapFilenamePrefix": "test-", + "dataPath": "data-test", + "tempDataPath": "data-test/_temp", + "listsPath": "lists-test", + "storagePolicy": "FOLLOWED_OR_VIEWED", + "maxStorageCapacity": 104857600, + "arrrDefaultBirthday": 1900000 +} diff --git a/src/test/resources/test-settings-v2.json b/src/test/resources/test-settings-v2.json index b2ad3db8..0a604efa 100644 --- a/src/test/resources/test-settings-v2.json +++ b/src/test/resources/test-settings-v2.json @@ -15,5 +15,6 @@ "tempDataPath": "data-test/_temp", "listsPath": "lists-test", "storagePolicy": "FOLLOWED_OR_VIEWED", - "maxStorageCapacity": 104857600 + "maxStorageCapacity": 104857600, + "arrrDefaultBirthday": 1900000 } diff --git a/tools/approve-dev-transaction.sh b/tools/approve-dev-transaction.sh new file mode 100755 index 00000000..6b611b59 --- /dev/null +++ b/tools/approve-dev-transaction.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +port=12391 +if [ $# -gt 0 -a "$1" = "-t" ]; then + port=62391 +fi + +printf "Searching for auto-update transactions to approve...\n"; + +tx=$( curl --silent --url "http://localhost:${port}/transactions/search?txGroupId=1&txType=ADD_GROUP_ADMIN&txType=REMOVE_GROUP_ADMIN&confirmationStatus=CONFIRMED&limit=1&reverse=true" ); +if fgrep --silent '"approvalStatus":"PENDING"' <<< "${tx}"; then + true +else + echo "Can't find any pending transactions" + exit +fi + +sig=$( perl -n -e 'print $1 if m/"signature":"(\w+)"/' <<< "${tx}" ) +if [ -z "${sig}" ]; then + printf "Can't find transaction signature in JSON:\n%s\n" "${tx}" + exit +fi + +printf "Found transaction %s\n" $sig; + +printf "\nPaste your dev account private key:\n"; +IFS= +read -s privkey +printf "\n" + +# Convert to public key +pubkey=$( curl --silent --url "http://localhost:${port}/utils/publickey" --data @- <<< "${privkey}" ); +if egrep -v --silent '^\w{44,46}$' <<< "${pubkey}"; then + printf "Invalid response from API - was your private key correct?\n%s\n" "${pubkey}" + exit +fi +printf "Your public key: %s\n" ${pubkey} + +# Convert to address +address=$( curl --silent --url "http://localhost:${port}/addresses/convert/${pubkey}" ); +printf "Your address: %s\n" ${address} + +# Grab last reference +lastref=$( curl --silent --url "http://localhost:${port}/addresses/lastreference/{$address}" ); +printf "Your last reference: %s\n" ${lastref} + +# Build GROUP_APPROVAL transaction +timestamp=$( date +%s )000 +tx_json=$( cat < 0; seconds--)); do + if [ "${seconds}" = "1" ]; then + plural="" + fi + printf "\rBroadcasting in %d second%s...(CTRL-C) to abort " $seconds $plural + sleep 1 +done + +printf "\rBroadcasting signed GROUP_APPROVAL transaction... \n" +result=$( curl --silent --url "http://localhost:${port}/transactions/process" --data @- <<< "${signed_tx}" ) +printf "API response:\n%s\n" "${result}" diff --git a/tools/build-zip.sh b/tools/build-zip.sh index b52b5da7..f423bca1 100755 --- a/tools/build-zip.sh +++ b/tools/build-zip.sh @@ -58,6 +58,9 @@ git show HEAD:log4j2.properties > ${build_dir}/log4j2.properties git show HEAD:start.sh > ${build_dir}/start.sh git show HEAD:stop.sh > ${build_dir}/stop.sh +chmod +x ${build_dir}/start.sh +chmod +x ${build_dir}/stop.sh + printf "{\n}\n" > ${build_dir}/settings.json gtouch -d ${commit_ts%%+??:??} ${build_dir} ${build_dir}/* diff --git a/tools/publish-auto-update-v5.pl b/tools/publish-auto-update-v5.pl index aad49d4e..f97fe115 100755 --- a/tools/publish-auto-update-v5.pl +++ b/tools/publish-auto-update-v5.pl @@ -4,6 +4,7 @@ use strict; use warnings; use POSIX; use Getopt::Std; +use File::Slurp; sub usage() { die("usage: $0 [-p api-port] dev-private-key [short-commit-hash]\n"); @@ -34,6 +35,8 @@ while () { } close(POM); +my $apikey = read_file('apikey.txt'); + # Do we need to determine commit hash? unless ($commit_hash) { # determine git branch @@ -124,7 +127,7 @@ my $raw_tx = `curl --silent --url http://localhost:${port}/utils/tobase58/${raw_ die("Can't convert raw transaction hex to base58:\n$raw_tx\n") unless $raw_tx =~ m/^\w{300,320}$/; # Roughly 305 to 320 base58 chars printf "\nRaw transaction (base58):\n%s\n", $raw_tx; -my $computed_tx = `curl --silent -X POST --url http://localhost:${port}/arbitrary/compute -d "${raw_tx}"`; +my $computed_tx = `curl --silent -X POST --url http://localhost:${port}/arbitrary/compute -H "X-API-KEY: ${apikey}" -d "${raw_tx}"`; die("Can't compute nonce for transaction:\n$computed_tx\n") unless $computed_tx =~ m/^\w{300,320}$/; # Roughly 300 to 320 base58 chars printf "\nRaw computed transaction (base58):\n%s\n", $computed_tx; diff --git a/tools/qdata b/tools/qdn similarity index 99% rename from tools/qdata rename to tools/qdn index 5ca61e45..869bf5c4 100755 --- a/tools/qdata +++ b/tools/qdn @@ -2,7 +2,7 @@ # Qortal defaults host="localhost" -port=12393 +port=12391 if [ -z "$*" ]; then echo "Usage:" diff --git a/tools/tx.pl b/tools/tx.pl index db6958e2..fe3cd872 100755 --- a/tools/tx.pl +++ b/tools/tx.pl @@ -71,9 +71,14 @@ our %TRANSACTION_TYPES = ( }, add_group_admin => { url => 'groups/addadmin', - required => [qw(groupId member)], + required => [qw(groupId txGroupId member)], key_name => 'ownerPublicKey', }, + remove_group_admin => { + url => 'groups/removeadmin', + required => [qw(groupId txGroupId admin)], + key_name => 'ownerPublicKey', + }, group_approval => { url => 'groups/approval', required => [qw(pendingSignature approval)],