From 6fd0284a58b64df66c3fc940721fd4bac8cbc9a9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Apr 2019 12:51:29 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .idea/gradle.xml | 2 +- .idea/misc.xml | 35 - app/build.gradle | 2 +- .../tangem/domain/wallet/xrp/XrpEngine.java | 13 +- .../tangem/domain/wallet/xrp/XrpPayment.java | 17 + .../wallet/xrp/XrpSignedTransaction.java | 29 +- ripple-core-0.0.1/build.gradle | 2 + .../ripple-core-0.0.1-SNAPSHOT.jar | Bin 0 -> 294113 bytes ripple-core/.gitignore | 1 - ripple-core/README.md | 391 --------- ripple-core/build.gradle | 52 -- ripple-core/src/main/java/README.java | 206 ----- .../com/ripple/core/binary/FileSTWriter.java | 32 - .../java/com/ripple/core/binary/STReader.java | 126 --- .../java/com/ripple/core/binary/STWriter.java | 71 -- .../java/com/ripple/core/cache/SLECache.java | 110 --- .../com/ripple/core/coretypes/AccountID.java | 188 ----- .../com/ripple/core/coretypes/Amount.java | 790 ------------------ .../java/com/ripple/core/coretypes/Blob.java | 119 --- .../com/ripple/core/coretypes/Currency.java | 250 ------ .../java/com/ripple/core/coretypes/Flags.java | 35 - .../java/com/ripple/core/coretypes/Issue.java | 101 --- .../com/ripple/core/coretypes/IssuePair.java | 36 - .../com/ripple/core/coretypes/PathSet.java | 273 ------ .../com/ripple/core/coretypes/Quality.java | 26 - .../com/ripple/core/coretypes/RippleDate.java | 82 -- .../com/ripple/core/coretypes/STArray.java | 129 --- .../com/ripple/core/coretypes/STObject.java | 489 ----------- .../core/coretypes/STObjectFormatter.java | 170 ---- .../com/ripple/core/coretypes/Vector256.java | 141 ---- .../core/coretypes/hash/HalfSha512.java | 68 -- .../com/ripple/core/coretypes/hash/Hash.java | 155 ---- .../ripple/core/coretypes/hash/Hash128.java | 70 -- .../ripple/core/coretypes/hash/Hash160.java | 81 -- .../ripple/core/coretypes/hash/Hash256.java | 146 ---- .../com/ripple/core/coretypes/hash/Index.java | 159 ---- .../coretypes/hash/prefixes/HashPrefix.java | 56 -- .../coretypes/hash/prefixes/LedgerSpace.java | 38 - .../core/coretypes/hash/prefixes/Prefix.java | 5 - .../com/ripple/core/coretypes/uint/UInt.java | 209 ----- .../ripple/core/coretypes/uint/UInt16.java | 106 --- .../ripple/core/coretypes/uint/UInt32.java | 137 --- .../ripple/core/coretypes/uint/UInt64.java | 112 --- .../com/ripple/core/coretypes/uint/UInt8.java | 105 --- .../com/ripple/core/enums/LedgerFlag.java | 29 - .../ripple/core/enums/TransactionFlag.java | 50 -- .../ripple/core/fields/AccountIDField.java | 3 - .../com/ripple/core/fields/AmountField.java | 3 - .../com/ripple/core/fields/BlobField.java | 3 - .../java/com/ripple/core/fields/Field.java | 328 -------- .../java/com/ripple/core/fields/HasField.java | 5 - .../com/ripple/core/fields/Hash128Field.java | 3 - .../com/ripple/core/fields/Hash160Field.java | 3 - .../com/ripple/core/fields/Hash256Field.java | 3 - .../com/ripple/core/fields/PathSetField.java | 3 - .../com/ripple/core/fields/STArrayField.java | 3 - .../com/ripple/core/fields/STObjectField.java | 3 - .../java/com/ripple/core/fields/Type.java | 35 - .../com/ripple/core/fields/UInt16Field.java | 3 - .../com/ripple/core/fields/UInt32Field.java | 3 - .../com/ripple/core/fields/UInt64Field.java | 3 - .../com/ripple/core/fields/UInt8Field.java | 3 - .../ripple/core/fields/Vector256Field.java | 3 - .../java/com/ripple/core/formats/Format.java | 66 -- .../com/ripple/core/formats/LEFormat.java | 230 ----- .../com/ripple/core/formats/TxFormat.java | 212 ----- .../java/com/ripple/core/runtime/Value.java | 111 --- .../ripple/core/serialized/BinaryParser.java | 99 --- .../core/serialized/BinarySerializer.java | 94 --- .../com/ripple/core/serialized/BytesList.java | 77 -- .../com/ripple/core/serialized/BytesSink.java | 8 - .../com/ripple/core/serialized/MultiSink.java | 16 - .../core/serialized/SerializedType.java | 11 - .../core/serialized/StreamBinaryParser.java | 81 -- .../ripple/core/serialized/StreamSink.java | 31 - .../core/serialized/TypeTranslator.java | 151 ---- .../core/serialized/enums/EngineResult.java | 258 ------ .../serialized/enums/LedgerEntryType.java | 101 --- .../serialized/enums/TransactionType.java | 103 --- .../core/types/known/generic/Validation.java | 15 - .../core/types/known/sle/LedgerEntry.java | 59 -- .../core/types/known/sle/LedgerHashes.java | 32 - .../types/known/sle/ThreadedLedgerEntry.java | 18 - .../types/known/sle/entries/AccountRoot.java | 71 -- .../types/known/sle/entries/Amendments.java | 22 - .../core/types/known/sle/entries/Check.java | 40 - .../known/sle/entries/DepositPreauthLe.java | 18 - .../known/sle/entries/DirectoryNode.java | 88 -- .../core/types/known/sle/entries/Escrow.java | 74 -- .../types/known/sle/entries/FeeSettings.java | 22 - .../types/known/sle/entries/IHasOwners.java | 12 - .../known/sle/entries/IndexedLedgerEntry.java | 56 -- .../core/types/known/sle/entries/Offer.java | 219 ----- .../known/sle/entries/OfferDirectory.java | 20 - .../known/sle/entries/OwnerDirectory.java | 3 - .../types/known/sle/entries/PayChannel.java | 42 - .../types/known/sle/entries/RippleState.java | 224 ----- .../types/known/sle/entries/SignerList.java | 32 - .../core/types/known/sle/entries/Ticket.java | 27 - .../core/types/known/tx/Transaction.java | 116 --- .../types/known/tx/result/AffectedNode.java | 156 ---- .../known/tx/result/TransactionMeta.java | 50 -- .../known/tx/result/TransactionResult.java | 299 ------- .../core/types/known/tx/txns/AccountSet.java | 46 - .../core/types/known/tx/txns/CheckCancel.java | 14 - .../core/types/known/tx/txns/CheckCash.java | 23 - .../core/types/known/tx/txns/CheckCreate.java | 30 - .../types/known/tx/txns/DepositPreauth.java | 19 - .../types/known/tx/txns/EscrowCancel.java | 18 - .../types/known/tx/txns/EscrowCreate.java | 33 - .../types/known/tx/txns/EscrowFinish.java | 27 - .../core/types/known/tx/txns/OfferCancel.java | 13 - .../core/types/known/tx/txns/OfferCreate.java | 25 - .../core/types/known/tx/txns/Payment.java | 39 - .../known/tx/txns/PaymentChannelClaim.java | 31 - .../known/tx/txns/PaymentChannelCreate.java | 32 - .../known/tx/txns/PaymentChannelFund.java | 24 - .../types/known/tx/txns/SetRegularKey.java | 14 - .../types/known/tx/txns/SignerListSet.java | 20 - .../types/known/tx/txns/TicketCancel.java | 17 - .../types/known/tx/txns/TicketCreate.java | 22 - .../core/types/known/tx/txns/TrustSet.java | 25 - .../known/tx/txns/pseudo/EnableAmendment.java | 17 - .../types/known/tx/txns/pseudo/SetFee.java | 25 - .../core/types/ledger/LedgerHeader.java | 109 --- .../core/types/shamap/AccountState.java | 341 -------- .../types/shamap/AccountStateBuilder.java | 320 ------- .../ripple/core/types/shamap/BytesItem.java | 37 - .../core/types/shamap/HashedTreeWalker.java | 8 - .../ripple/core/types/shamap/LeafWalker.java | 5 - .../core/types/shamap/LedgerEntryItem.java | 45 - .../core/types/shamap/LedgerEntryVisitor.java | 7 - .../ripple/core/types/shamap/NodeStore.java | 64 -- .../ripple/core/types/shamap/PathToIndex.java | 156 ---- .../com/ripple/core/types/shamap/README.md | 319 ------- .../com/ripple/core/types/shamap/ShaMap.java | 30 - .../ripple/core/types/shamap/ShaMapDiff.java | 122 --- .../ripple/core/types/shamap/ShaMapInner.java | 331 -------- .../ripple/core/types/shamap/ShaMapItem.java | 11 - .../ripple/core/types/shamap/ShaMapLeaf.java | 34 - .../ripple/core/types/shamap/ShaMapNode.java | 47 -- .../types/shamap/TransactionResultItem.java | 38 - .../shamap/TransactionResultVisitor.java | 6 - .../core/types/shamap/TransactionTree.java | 43 - .../ripple/core/types/shamap/TreeWalker.java | 6 - .../src/main/java/com/ripple/crypto/Seed.java | 85 -- .../ripple/crypto/ecdsa/ECDSASignature.java | 155 ---- .../java/com/ripple/crypto/ecdsa/K256.java | 103 --- .../com/ripple/crypto/ecdsa/K256KeyPair.java | 54 -- .../ripple/crypto/ecdsa/K256VerifyingKey.java | 55 -- .../com/ripple/crypto/ecdsa/SECP256K1.java | 42 - .../com/ripple/crypto/ed25519/ED25519.java | 9 - .../com/ripple/crypto/ed25519/EDKeyPair.java | 52 -- .../ripple/crypto/ed25519/EDVerifyingKey.java | 58 -- .../java/com/ripple/crypto/keys/IKeyPair.java | 6 - .../com/ripple/crypto/keys/IVerifyingKey.java | 29 - .../ripple/encodings/addresses/Addresses.java | 77 -- .../java/com/ripple/encodings/base58/B58.java | 9 - .../com/ripple/encodings/basex/BaseX.java | 231 ----- .../basex/EncodingFormatException.java | 7 - .../com/ripple/encodings/basex/IBaseX.java | 41 - .../java/com/ripple/encodings/common/B16.java | 20 - .../java/com/ripple/encodings/common/B64.java | 12 - .../main/java/com/ripple/utils/HashUtils.java | 62 -- .../main/java/com/ripple/utils/Sha512.java | 53 -- .../src/main/java/com/ripple/utils/Utils.java | 58 -- settings.gradle | 2 +- 167 files changed, 33 insertions(+), 12998 deletions(-) create mode 100644 app/src/main/java/com/tangem/domain/wallet/xrp/XrpPayment.java rename ripple-core/src/main/java/com/ripple/core/types/known/tx/signed/SignedTransaction.java => app/src/main/java/com/tangem/domain/wallet/xrp/XrpSignedTransaction.java (74%) create mode 100644 ripple-core-0.0.1/build.gradle create mode 100644 ripple-core-0.0.1/ripple-core-0.0.1-SNAPSHOT.jar delete mode 100644 ripple-core/.gitignore delete mode 100644 ripple-core/README.md delete mode 100644 ripple-core/build.gradle delete mode 100644 ripple-core/src/main/java/README.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/binary/FileSTWriter.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/binary/STReader.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/binary/STWriter.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/cache/SLECache.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/AccountID.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/Amount.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/Blob.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/Currency.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/Flags.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/Issue.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/IssuePair.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/PathSet.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/Quality.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/RippleDate.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/STArray.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/STObject.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/STObjectFormatter.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/Vector256.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/HalfSha512.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash128.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash160.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash256.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/Index.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/HashPrefix.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/LedgerSpace.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/Prefix.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt16.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt32.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt64.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt8.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/enums/LedgerFlag.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/enums/TransactionFlag.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/AccountIDField.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/AmountField.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/BlobField.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/HasField.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/Hash128Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/Hash160Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/Hash256Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/PathSetField.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/STArrayField.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/STObjectField.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/Type.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/UInt16Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/UInt32Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/UInt64Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/UInt8Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/fields/Vector256Field.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/formats/Format.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/formats/LEFormat.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/formats/TxFormat.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/runtime/Value.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/BinaryParser.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/BinarySerializer.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/BytesList.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/BytesSink.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/MultiSink.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/SerializedType.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/StreamBinaryParser.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/StreamSink.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/TypeTranslator.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/enums/EngineResult.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/enums/LedgerEntryType.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/serialized/enums/TransactionType.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/generic/Validation.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/LedgerEntry.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/LedgerHashes.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/ThreadedLedgerEntry.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/AccountRoot.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Amendments.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Check.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/DepositPreauthLe.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/DirectoryNode.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Escrow.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/FeeSettings.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/IHasOwners.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/IndexedLedgerEntry.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Offer.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/OfferDirectory.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/OwnerDirectory.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/PayChannel.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/RippleState.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/SignerList.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Ticket.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/Transaction.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/result/AffectedNode.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/result/TransactionMeta.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/result/TransactionResult.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/AccountSet.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCancel.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCash.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCreate.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/DepositPreauth.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowCancel.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowCreate.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowFinish.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/OfferCancel.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/OfferCreate.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/Payment.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelClaim.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelCreate.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelFund.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/SetRegularKey.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/SignerListSet.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TicketCancel.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TicketCreate.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TrustSet.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/pseudo/EnableAmendment.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/pseudo/SetFee.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/ledger/LedgerHeader.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/AccountState.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/AccountStateBuilder.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/BytesItem.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/HashedTreeWalker.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/LeafWalker.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/LedgerEntryItem.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/LedgerEntryVisitor.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/NodeStore.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/PathToIndex.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/README.md delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMap.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapDiff.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapInner.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapItem.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapLeaf.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapNode.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionResultItem.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionResultVisitor.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionTree.java delete mode 100644 ripple-core/src/main/java/com/ripple/core/types/shamap/TreeWalker.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/Seed.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/ecdsa/ECDSASignature.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256KeyPair.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256VerifyingKey.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/ecdsa/SECP256K1.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/ed25519/ED25519.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/ed25519/EDKeyPair.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/ed25519/EDVerifyingKey.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/keys/IKeyPair.java delete mode 100644 ripple-core/src/main/java/com/ripple/crypto/keys/IVerifyingKey.java delete mode 100644 ripple-core/src/main/java/com/ripple/encodings/addresses/Addresses.java delete mode 100644 ripple-core/src/main/java/com/ripple/encodings/base58/B58.java delete mode 100644 ripple-core/src/main/java/com/ripple/encodings/basex/BaseX.java delete mode 100644 ripple-core/src/main/java/com/ripple/encodings/basex/EncodingFormatException.java delete mode 100644 ripple-core/src/main/java/com/ripple/encodings/basex/IBaseX.java delete mode 100644 ripple-core/src/main/java/com/ripple/encodings/common/B16.java delete mode 100644 ripple-core/src/main/java/com/ripple/encodings/common/B64.java delete mode 100644 ripple-core/src/main/java/com/ripple/utils/HashUtils.java delete mode 100644 ripple-core/src/main/java/com/ripple/utils/Sha512.java delete mode 100644 ripple-core/src/main/java/com/ripple/utils/Utils.java diff --git a/.idea/gradle.xml b/.idea/gradle.xml index dbe604a60c..da117a274a 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -11,7 +11,7 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 8e44652cdd..703e5d4b89 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,41 +5,6 @@ - - - - diff --git a/app/build.gradle b/app/build.gradle index d6bf56e099..dff789a723 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,10 +44,10 @@ android { } dependencies { - implementation project(':ripple-core') implementation project(':card-common') implementation project(':card-android') implementation project(':server-android') + implementation project(':ripple-core-0.0.1') // implementation 'com.github.TangemCash:card-common:0.1.2' // implementation 'com.github.TangemCash:card-android:0.1.3' diff --git a/app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java index 4cd6d5b250..d79b9ca4cd 100644 --- a/app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java @@ -6,8 +6,6 @@ import android.util.Log; import com.ripple.core.coretypes.AccountID; import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.types.known.tx.signed.SignedTransaction; -import com.ripple.core.types.known.tx.txns.Payment; import com.ripple.crypto.ecdsa.ECDSASignature; import com.ripple.encodings.addresses.Addresses; import com.ripple.utils.HashUtils; @@ -26,10 +24,6 @@ import com.tangem.util.CryptoUtil; import com.tangem.util.DecimalDigitsInputFilter; import com.tangem.wallet.R; -import org.spongycastle.asn1.ASN1EncodableVector; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DERSequence; - import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; @@ -364,7 +358,7 @@ public class XrpEngine extends CoinEngine { fee = Long.toString(convertToInternalAmount(feeValue).longValueExact()); - Payment payment = new Payment(); + XrpPayment payment = new XrpPayment(); // Put `as` AccountID field Account, `Object` o payment.as(AccountID.Account, coinData.getWallet()); @@ -373,7 +367,7 @@ public class XrpEngine extends CoinEngine { payment.as(UInt32.Sequence, coinData.getSequence()); payment.as(com.ripple.core.coretypes.Amount.Fee, fee); - SignedTransaction signedTx = payment.prepare(canonisePubKey(ctx.getCard().getWalletPublicKeyRar())); + XrpSignedTransaction signedTx = payment.prepare(canonisePubKey(ctx.getCard().getWalletPublicKeyRar())); return new SignTask.TransactionToSign() { @@ -449,8 +443,7 @@ public class XrpEngine extends CoinEngine { throw new Exception("Invalid wallet address in answer!"); } coinData.setBalanceConfirmed(Long.parseLong(rippleResponse.getResult().getAccount_data().getBalance())); - } - else if (rippleResponse.getResult().getError_code().equals(19)) // "Account not found" + } else if (rippleResponse.getResult().getError_code().equals(19)) // "Account not found" coinData.setAccountNotFound(true); } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/com/tangem/domain/wallet/xrp/XrpPayment.java b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpPayment.java new file mode 100644 index 0000000000..1d202c922f --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpPayment.java @@ -0,0 +1,17 @@ +package com.tangem.domain.wallet.xrp; + +import com.ripple.core.types.known.tx.txns.Payment; + +public class XrpPayment extends Payment { + + public XrpPayment() { + super(); + } + + public XrpSignedTransaction prepare(byte[] pubKeyBytes) { + XrpSignedTransaction tx = XrpSignedTransaction.fromTx(this); + tx.prepare(pubKeyBytes); + return tx; + } + +} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/signed/SignedTransaction.java b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpSignedTransaction.java similarity index 74% rename from ripple-core/src/main/java/com/ripple/core/types/known/tx/signed/SignedTransaction.java rename to app/src/main/java/com/tangem/domain/wallet/xrp/XrpSignedTransaction.java index 56440a9bf1..29babc095b 100644 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/signed/SignedTransaction.java +++ b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpSignedTransaction.java @@ -1,41 +1,32 @@ -package com.ripple.core.types.known.tx.signed; +package com.tangem.domain.wallet.xrp; import com.ripple.core.coretypes.Amount; import com.ripple.core.coretypes.Blob; import com.ripple.core.coretypes.STObject; import com.ripple.core.coretypes.hash.HalfSha512; -import com.ripple.core.coretypes.hash.Hash256; import com.ripple.core.coretypes.hash.prefixes.HashPrefix; import com.ripple.core.coretypes.uint.UInt32; import com.ripple.core.serialized.BytesList; import com.ripple.core.serialized.MultiSink; -import com.ripple.core.serialized.enums.TransactionType; import com.ripple.core.types.known.tx.Transaction; +import com.ripple.core.types.known.tx.signed.SignedTransaction; import java.util.Arrays; -public class SignedTransaction { - private SignedTransaction(Transaction of) { - // TODO: is this just over kill ? +public class XrpSignedTransaction extends SignedTransaction { + private XrpSignedTransaction(Transaction of) { txn = (Transaction) STObject.fromBytes(of.toBytes()); } - protected SignedTransaction() { + protected XrpSignedTransaction() { } - public Transaction txn; - public Hash256 hash; - - public byte[] signingData; - public byte[] previousSigningData; - public String tx_blob; - - public static SignedTransaction fromTx(Transaction tx) { - return new SignedTransaction(tx); + public static XrpSignedTransaction fromTx(Transaction tx) { + return new XrpSignedTransaction(tx); } public void prepare(byte[] pubKeyBytes) { - prepare(pubKeyBytes,null, null, null); + prepare(pubKeyBytes, null, null, null); } public void prepare(byte[] pubKeyBytes, @@ -87,8 +78,4 @@ public class SignedTransaction { previousSigningData = signingData; // } } - - public TransactionType transactionType() { - return txn.transactionType(); - } } diff --git a/ripple-core-0.0.1/build.gradle b/ripple-core-0.0.1/build.gradle new file mode 100644 index 0000000000..31be8312e1 --- /dev/null +++ b/ripple-core-0.0.1/build.gradle @@ -0,0 +1,2 @@ +configurations.maybeCreate("default") +artifacts.add("default", file('ripple-core-0.0.1-SNAPSHOT.jar')) \ No newline at end of file diff --git a/ripple-core-0.0.1/ripple-core-0.0.1-SNAPSHOT.jar b/ripple-core-0.0.1/ripple-core-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..0600aabbdbdf9d9af603a1a0e36a8b640acad177 GIT binary patch literal 294113 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∈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 literal 0 HcmV?d00001 diff --git a/ripple-core/.gitignore b/ripple-core/.gitignore deleted file mode 100644 index ea8c4bf7f3..0000000000 --- a/ripple-core/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/ripple-core/README.md b/ripple-core/README.md deleted file mode 100644 index d89d96f5e8..0000000000 --- a/ripple-core/README.md +++ /dev/null @@ -1,391 +0,0 @@ -# SerializedTypes Overview - -Ripple uses a node store, where objects are keyed by a 32byte hash value. - -These keys are created by hashing a binary representation of the whole object -prefixed with a name-spacing sequence of bytes, unique to each class of object. -As such, a method of consistently producing a binary sequence from a given -object was required. - -Objects have fields, which are a pairing of a type and a name. Names are simply -ordinals used to look up preassigned names in a per type table. The type ordinal, -similarly, is used to lookup a given class. - -In ripple-lib-java the type equates to a `SerializedType`, with types such as 32 -bit unsigned integer, variable length byte strings and even arrays of objects. -The fields have an ordinal quality, and can be deterministically sorted, -important for consistent binary representation. - -There are also container types: An STObject, short for `SerializedType Object`, -is an associative container of fields to other SerializedTypes (even an STObject -itself). An STArray is an array of STObjects with a single Field, mapped to an -STObject, containing an arbitrary amount of Field -> SerializedType pairs. (See -notes below on differences to C++ implementation) - -## Types - -Following is a survey of some of the classes, sorted somewhat topologically. - -The following json representation of metadata related to a finalized transaction -can be used as a concrete example and will be referenced later. - -```java -/** - * - * # Transaction Meta JSON - * - * { - * "TransactionResult": "tesSUCCESS", - * - * {@link com.ripple.core.types.known.tx.result.TransactionResult} - * {@link com.ripple.core.fields.Field.TransactionResult} - * {@link com.ripple.core.coretypes.uint.UInt8} - * - * "TransactionIndex": 0, - * - * {@link com.ripple.core.types.known.tx.result.TransactionMeta#transactionIndex} - * {@link com.ripple.core.coretypes.uint.UInt32} - * - * "AffectedNodes": [ - * - * {@link com.ripple.core.types.known.tx.result.TransactionMeta#affectedNodes} - * - * { - - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode} - - * - * "NewFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsFinal()} - * - * "Sequence": 103929, - * - * {@link com.ripple.core.fields.Field#Sequence} - * - * - * "TakerGets": { - * - * {@link com.ripple.core.coretypes.Amount} - * - * "currency": "ILS", - * - * {@link com.ripple.core.coretypes.Currency} - * - * "value": "1694.768", - * - * {@link com.ripple.core.coretypes.AccountID} - * - * "issuer": "rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9" - * - * {@link java.math.BigDecimal} - * - * }, - * - * - * "Account": "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", - * - * {@link com.ripple.core.coretypes.AccountID} - * - * "BookDirectory": "62A3338CAF2E1BEE510FC33DE1863C56948E962CCE173CA55C14BE8A20D7F000", - * - * {@link com.ripple.core.coretypes.Quality#fromBookDirectory} - * - * "TakerPays": "98957503520", - * - * {@link com.ripple.core.coretypes.Amount} - * {@link com.ripple.core.coretypes.Amount#isNative()} - * - * "OwnerNode": "000000000000000E" - * - * {@link com.ripple.core.types.known.sle.entries.Offer#ownerNodeDirectoryIndex} - * }, - * - * "LedgerIndex": "3596CE72C902BAFAAB56CC486ACAF9B4AFC67CF7CADBB81A4AA9CBDC8C5CB1AA", - * - * {@link com.ripple.core.coretypes.hash.Hash256#index} - * - * "LedgerEntryType": "Offer" - * - * {@link com.ripple.core.serialized.enums.LedgerEntryType} - * {@link com.ripple.core.types.known.sle.entries.Offer} - * } - * }, - * - * - * { - - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode} - - * - * "NewFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsFinal()} - * - * "RootIndex": "62A3338CAF2E1BEE510FC33DE1863C56948E962CCE173CA55C14BE8A20D7F000", - * - * com.ripple.core.coretypes.hash.Hash256#index - * - * "TakerGetsIssuer": "92D705968936C419CE614BF264B5EEB1CEA47FF4", - * - * {@link com.ripple.core.coretypes.Currency} - * {@link com.ripple.core.coretypes.hash.Hash160} - * {@link com.ripple.core.fields.Field#TakerGetsIssuer} - * - * "ExchangeRate": "5C14BE8A20D7F000", - * - * {@link com.ripple.core.coretypes.Quality#fromBookDirectory} - * - * "TakerGetsCurrency": "000000000000000000000000494C530000000000" - * - * {@link com.ripple.core.coretypes.Currency} - * {@link com.ripple.core.coretypes.hash.Hash160} - * {@link com.ripple.core.fields.Field#TakerGetsCurrency} - * - * }, - * - * "LedgerIndex": "62A3338CAF2E1BEE510FC33DE1863C56948E962CCE173CA55C14BE8A20D7F000", - * - * {@link com.ripple.core.coretypes.hash.Hash256#index} - * - * "LedgerEntryType": "DirectoryNode" - * - * {@link com.ripple.core.types.known.sle.entries.DirectoryNode} - * {@link com.ripple.core.serialized.enums.LedgerEntryType#DirectoryNode} - * } - * }, - * - * { - * "ModifiedNode": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode} - * {@link com.ripple.core.types.known.tx.result.AffectedNode#isModifiedNode()} - * - * "FinalFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsFinal()} - * - * "RootIndex": "801C5AFB5862D4666D0DF8E5BE1385DC9B421ED09A4269542A07BC0267584B64", - * - * {@link com.ripple.core.types.known.sle.entries.DirectoryNode#rootIndex} - * - * "Flags": 0, - * "Owner": "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", - * - * {@link com.ripple.core.coretypes.hash.Index#ownerDirectory} - * - * "IndexPrevious": "0000000000000000" - * - * {@link com.ripple.core.types.known.sle.entries.DirectoryNode#hasPreviousIndex} - * {@link com.ripple.core.types.known.sle.entries.DirectoryNode#prevIndex} - * }, - * - * "LedgerIndex": "AB03F8AA02FFA4635E7CE2850416AEC5542910A2B4DBE93C318FEB08375E0DB5", - * - * "LedgerEntryType": "DirectoryNode" - * } - * }, - * { - * "ModifiedNode": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode} - * {@link com.ripple.core.types.known.tx.result.AffectedNode#isModifiedNode()} - * - * "FinalFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsFinal()} - * - * "Sequence": 103930, - * "Flags": 0, - * "Account": "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", - * "OwnerCount": 9, - * "Balance": "106861218302" - * - * {@link com.ripple.core.coretypes.Amount} - * }, - * - * "LedgerIndex": "CF23A37E39A571A0F22EC3E97EB0169936B520C3088963F16C5EE4AC59130B1B", - * - * {@link com.ripple.core.coretypes.hash.Index#accountRoot} - * - * "LedgerEntryType": "AccountRoot", - * - * {@link com.ripple.core.types.known.sle.entries.AccountRoot} - * {@link com.ripple.core.serialized.enums.LedgerEntryType#AccountRoot} - * - * "PreviousFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsPrevious()} - * - * "Sequence": 103929, - * "OwnerCount": 8, - * "Balance": "106861218312" - * }, - * - * "PreviousTxnID": "DE15F43F4A73C4F6CB1C334D9E47BDE84467C0902796BB81D4924885D1C11E6D", - * - * {@link com.ripple.core.types.known.tx.Transaction#hash} - * - * "PreviousTxnLgrSeq": 3225338 - * - * {@link com.ripple.core.coretypes.uint.UInt32} - * } - * } - * ] - * } -*/ -``` - -* Note that ALL field names are, by convention, upper case (in fact field names (index, hash) may be -lower cased but are not serialized) - -* The `AffectedNodes` is an STArray. As stated, the immediate -children each contain only a single key (or [Field](src/main/java/com/ripple/core/fields/Field.java#L138-L140)) - - * ModifiedNode - -Moving on. - -``` -com -└── ripple - ├── serialized - ├── core - │ - ├── enums - │   ├── LedgerEntryType - │   ├── EngineResult - │   └── TransactionType -``` - -* In the json above look at the [TransactionResult](src/main/java/com/ripple/core/fields/Field.java#L164) field. - Note that it has a Type of of UINT8, yet clearly it's represented in json as a string. - -#### com.ripple.core.fields.Type - - This is a simple Java enum(eration) of the various types. eg. - - UINT32(2) - - This definition implies giving the static ordinal `2` to the UINT32 type. - -#### com.ripple.core.fields.Field - - Consider the following definition of a field - - QualityIn(20, Type.UINT32) - - As stated before a Field has name and type ordinals, but it also has an - implied symbolic string representation (as seen used in the json above) - - The string name can be looked up via a `code`, which is an integer created by - shifting the type ordinal 16 bits to the left and ORing it with the name. - - See: [com.ripple.core.fields.Field#fromCode](src/main/java/com/ripple/core/fields/Field.java) - -#### com.ripple.core.fields.HasField - - This is simply an interface for returning a Field. We know that a Field - implies a Type and a name and there's a set amount of them. For each concrete - class implementation of a given Type, we create a XXXfield class that - implements HasField - - eg. - - ```java - protected abstract static class STArrayField implements HasField{} - public static STArrayField starrayField(final Field f) { - return new STArrayField(){ @Override public Field getField() {return f;}}; - } - ``` - - Then we can create static members on the concrete class - - ```java - static public STArrayField AffectedNodes = starrayField(Field.AffectedNodes); - static public STArrayField Signatures = starrayField(Field.Signatures); - static public STArrayField Template = starrayField(Field.Template); - ``` - - Later this is used create an api that looks as so - - ```java - if (transactionType() == TransactionType.Payment && meta.has(STArray.AffectedNodes)) { - STArray affected = meta.get(STArray.AffectedNodes); - for (STObject node : affected) { - if (node.has(STObject.CreatedNode)) { - STObject created = node.get(STObject.CreatedNode); - ``` - - This is implemented by overloading get() - - ```java - public STArray get(STArray.STArrayField f) { - return (STArray) fields.get(f.getField()); - } - ``` - -### com.ripple.core.serialized - -#### com.ripple.core.serialized.SerializedType - -```java -public interface SerializedType { - Object toJSON(); - byte[] toBytes(); - String toHex(); - void toBytesSink(BytesSink to); - Type type(); -} -``` - -#### com.ripple.core.serialized.BytesList - -A dynamic array of byte[]. Used by TypeTranslators to avoid needless -copying (see fromParser(parser, hint)). - -#### com.ripple.core.serialized.BinaryParser - -Responsible for decoding Fields and VL encoded structures. - -#### com.ripple.core.serialized.TypeTranslator - -Handles converting a SerializedType instances to/from json, binary and other non -SerializedType values. - -Has methods like fromHex, fromBytes, which delegate to fromParser. - -#### com.ripple.core.serialized.BinarySerializer - -Responsible for encoding Fields/SerializeType into binary. - -## Notes - -* In the C++ implementation of serialized objects, an STObject can, itself, be - assigned a Field, which is stored outside of the associative structure. - - This is problematic when storing as json. Consider this pseudocode. - - ```python - >>> so = STObject() - >>> so.name = "FieldName" - >>> so["FieldOfDreams"] = "A Kevin Costner Movie" - >>> sa = STArray([so]) - ``` - - How could `sa` be declared as json? - - ```json - >>> [{"FieldName" : {"FieldOfDreams": "A Kevin Costner Movie"}}] - ``` - - This is in fact how rippled works. There is no 1:1 mapping of STObject to {} - - In ripple-lib(-java)? there is, and single key children of STArrays are enforced. - -* Ripple uses 32 byte hashes for object indexes, taking half of a SHA512 hash, - which is ~%33 faster than SHA256. - -* Simply using google protocol buffers was considered inadequate [link](https://github.com/ripple/rippled/blob/ee51968820fc41c5aeadf2067bfdae54ff21fa66/BinaryFormats.txt#L16) \ No newline at end of file diff --git a/ripple-core/build.gradle b/ripple-core/build.gradle deleted file mode 100644 index 7ee498ed2c..0000000000 --- a/ripple-core/build.gradle +++ /dev/null @@ -1,52 +0,0 @@ -apply plugin: 'java' -apply plugin: 'kotlin' - -description = 'ripple-core' - -version = '0.0.1-SNAPSHOT' - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -dependencies { - compile 'net.i2p.crypto:eddsa:0.2.0' - compile 'org.bouncycastle:bcprov-jdk15on:1.58' - compile 'org.json:json:20171018' - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.3' - testCompile 'junit:junit:4.12' - testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - testCompile "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.22.5" -} -buildscript { - ext.kotlin_version = '1.3.0' - repositories { - mavenCentral() - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} -repositories { - mavenCentral() -} -compileKotlin { - kotlinOptions { - jvmTarget = "1.8" - } -} -compileTestKotlin { - kotlinOptions { - jvmTarget = "1.8" - } -} - -task getDeps(type: Copy) { - from configurations.compile - into "$rootDir/jars/" -} - -kotlin { - experimental { - coroutines "enable" - } -} \ No newline at end of file diff --git a/ripple-core/src/main/java/README.java b/ripple-core/src/main/java/README.java deleted file mode 100644 index 0d01f2dfc7..0000000000 --- a/ripple-core/src/main/java/README.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * - * # Transaction Meta JSON - * - * { - * "TransactionResult": "tesSUCCESS", - * - * {@link com.ripple.core.types.known.tx.result.TransactionResult} - * {@link com.ripple.core.fields.Field.TransactionResult} - * {@link com.ripple.core.coretypes.uint.UInt8} - * - * "TransactionIndex": 0, - * - * {@link com.ripple.core.types.known.tx.result.TransactionMeta#transactionIndex} - * {@link com.ripple.core.coretypes.uint.UInt32} - * - * "AffectedNodes": [ - * - * {@link com.ripple.core.types.known.tx.result.TransactionMeta#affectedNodes} - * - * { - * "CreatedNode": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode} - * {@link com.ripple.core.types.known.tx.result.AffectedNode#isCreatedNode()} - * - * "NewFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsFinal()} - * - * "Sequence": 103929, - * - * {@link com.ripple.core.fields.Field#Sequence} - * - * - * "TakerGets": { - * - * {@link com.ripple.core.coretypes.Amount} - * - * "currency": "ILS", - * - * {@link com.ripple.core.coretypes.Currency} - * - * "value": "1694.768", - * - * {@link com.ripple.core.coretypes.AccountID} - * - * "issuer": "rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9" - * - * {@link java.math.BigDecimal} - * - * }, - * - * - * "Account": "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", - * - * {@link com.ripple.core.coretypes.AccountID} - * - * "BookDirectory": "62A3338CAF2E1BEE510FC33DE1863C56948E962CCE173CA55C14BE8A20D7F000", - * - * {@link com.ripple.core.coretypes.Quality#fromBookDirectory} - * - * "TakerPays": "98957503520", - * - * {@link com.ripple.core.coretypes.Amount} - * {@link com.ripple.core.coretypes.Amount#isNative()} - * - * "OwnerNode": "000000000000000E" - * - * {@link com.ripple.core.types.known.sle.entries.Offer#ownerNodeDirectoryIndex} - * }, - * - * "LedgerIndex": "3596CE72C902BAFAAB56CC486ACAF9B4AFC67CF7CADBB81A4AA9CBDC8C5CB1AA", - * - * {@link com.ripple.core.coretypes.hash.Hash256#index} - * - * "LedgerEntryType": "Offer" - * - * {@link com.ripple.core.serialized.enums.LedgerEntryType} - * {@link com.ripple.core.types.known.sle.entries.Offer} - * } - * }, - * - * - * { - * "CreatedNode": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode} - * {@link com.ripple.core.types.known.tx.result.AffectedNode#isCreatedNode()} - * - * "NewFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsFinal()} - * - * "RootIndex": "62A3338CAF2E1BEE510FC33DE1863C56948E962CCE173CA55C14BE8A20D7F000", - * - * com.ripple.core.coretypes.hash.Hash256#index - * - * "TakerGetsIssuer": "92D705968936C419CE614BF264B5EEB1CEA47FF4", - * - * {@link com.ripple.core.coretypes.Currency} - * {@link com.ripple.core.coretypes.hash.Hash160} - * {@link com.ripple.core.fields.Field#TakerGetsIssuer} - * - * "ExchangeRate": "5C14BE8A20D7F000", - * - * {@link com.ripple.core.coretypes.Quality#fromBookDirectory} - * - * "TakerGetsCurrency": "000000000000000000000000494C530000000000" - * - * {@link com.ripple.core.coretypes.Currency} - * {@link com.ripple.core.coretypes.hash.Hash160} - * {@link com.ripple.core.fields.Field#TakerGetsCurrency} - * - * }, - * - * "LedgerIndex": "62A3338CAF2E1BEE510FC33DE1863C56948E962CCE173CA55C14BE8A20D7F000", - * - * {@link com.ripple.core.coretypes.hash.Hash256#index} - * - * "LedgerEntryType": "DirectoryNode" - * - * {@link com.ripple.core.types.known.sle.entries.DirectoryNode} - * {@link com.ripple.core.serialized.enums.LedgerEntryType#DirectoryNode} - * } - * }, - * - * { - * "ModifiedNode": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode} - * {@link com.ripple.core.types.known.tx.result.AffectedNode#isModifiedNode()} - * - * "FinalFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsFinal()} - * - * "RootIndex": "801C5AFB5862D4666D0DF8E5BE1385DC9B421ED09A4269542A07BC0267584B64", - * - * {@link com.ripple.core.types.known.sle.entries.DirectoryNode#rootIndex} - * - * "Flags": 0, - * "Owner": "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", - * - * {@link com.ripple.core.coretypes.hash.Index#ownerDirectory} - * - * "IndexPrevious": "0000000000000000" - * - * {@link com.ripple.core.types.known.sle.entries.DirectoryNode#hasPreviousIndex} - * {@link com.ripple.core.types.known.sle.entries.DirectoryNode#prevIndex} - * }, - * - * "LedgerIndex": "AB03F8AA02FFA4635E7CE2850416AEC5542910A2B4DBE93C318FEB08375E0DB5", - * - * "LedgerEntryType": "DirectoryNode" - * } - * }, - * { - * "ModifiedNode": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode} - * {@link com.ripple.core.types.known.tx.result.AffectedNode#isModifiedNode()} - * - * "FinalFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsFinal()} - * - * "Sequence": 103930, - * "Flags": 0, - * "Account": "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", - * "OwnerCount": 9, - * "Balance": "106861218302" - * - * {@link com.ripple.core.coretypes.Amount} - * }, - * - * "LedgerIndex": "CF23A37E39A571A0F22EC3E97EB0169936B520C3088963F16C5EE4AC59130B1B", - * - * {@link com.ripple.core.coretypes.hash.Index#accountRoot} - * - * "LedgerEntryType": "AccountRoot", - * - * {@link com.ripple.core.types.known.sle.entries.AccountRoot} - * {@link com.ripple.core.serialized.enums.LedgerEntryType#AccountRoot} - * - * "PreviousFields": { - * - * {@link com.ripple.core.types.known.tx.result.AffectedNode#nodeAsPrevious()} - * - * "Sequence": 103929, - * "OwnerCount": 8, - * "Balance": "106861218312" - * }, - * - * "PreviousTxnID": "DE15F43F4A73C4F6CB1C334D9E47BDE84467C0902796BB81D4924885D1C11E6D", - * - * {@link com.ripple.core.types.known.tx.Transaction#hash} - * - * "PreviousTxnLgrSeq": 3225338 - * - * {@link com.ripple.core.coretypes.uint.UInt32} - * } - * } - * ] - * } -*/ \ No newline at end of file diff --git a/ripple-core/src/main/java/com/ripple/core/binary/FileSTWriter.java b/ripple-core/src/main/java/com/ripple/core/binary/FileSTWriter.java deleted file mode 100644 index e46af9db43..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/binary/FileSTWriter.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.ripple.core.binary; - -import com.ripple.core.serialized.StreamSink; - -import java.io.*; - -public class FileSTWriter extends STWriter implements Closeable { - BufferedOutputStream out; - - private FileSTWriter(StreamSink sink, BufferedOutputStream out) { - super(sink); - this.out = out; - } - - public static FileSTWriter fromFile(String path) { - FileOutputStream fos = null; - try { - fos = new FileOutputStream(path); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - BufferedOutputStream out = new BufferedOutputStream(fos); - StreamSink sink = new StreamSink(out); - - return new FileSTWriter(sink, out); - } - - @Override - public void close() throws IOException { - out.close(); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/binary/STReader.java b/ripple-core/src/main/java/com/ripple/core/binary/STReader.java deleted file mode 100644 index dc3655073b..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/binary/STReader.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.ripple.core.binary; - -import com.ripple.core.coretypes.*; -import com.ripple.core.coretypes.hash.Hash128; -import com.ripple.core.coretypes.hash.Hash160; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.uint.UInt16; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.coretypes.uint.UInt8; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.StreamBinaryParser; -import com.ripple.core.types.known.sle.LedgerEntry; -import com.ripple.core.types.known.tx.Transaction; -import com.ripple.core.types.known.tx.result.TransactionMeta; -import com.ripple.core.types.known.tx.result.TransactionResult; - -import java.util.Arrays; -import java.util.Date; - -public class STReader { - protected BinaryParser parser; - public STReader(BinaryParser parser) { - this.parser = parser; - } - public STReader(String hex) { - this.parser = new BinaryParser(hex); - } - - public static STReader fromFile(String arg) { - return new STReader(StreamBinaryParser.fromFile(arg)); - } - - public UInt8 uInt8() { - return UInt8.fromParser(parser); - } - public UInt16 uInt16() { - return UInt16.fromParser(parser); - } - public UInt32 uInt32() { - return UInt32.fromParser(parser); - } - public UInt64 uInt64() { - return UInt64.fromParser(parser); - } - public Hash128 hash128() { - return Hash128.fromParser(parser); - } - public Hash160 hash160() { - return Hash160.fromParser(parser); - } - public Currency currency() { - return Currency.fromParser(parser); - } - public Hash256 hash256() { - return Hash256.fromParser(parser); - } - public Vector256 vector256() { - return Vector256.fromParser(parser); - } - public AccountID accountID() { - return AccountID.fromParser(parser); - } - public Blob variableLength() { - int hint = parser.readVLLength(); - return Blob.fromParser(parser, hint); - } - public Amount amount() { - return Amount.fromParser(parser); - } - public PathSet pathSet() { - return PathSet.fromParser(parser); - } - - public STObject stObject() { - return STObject.fromParser(parser); - } - public STObject vlStObject() { - return STObject.fromParser(parser, parser.readVLLength()); - } - - public HashPrefix hashPrefix() { - byte[] read = parser.read(4); - for (HashPrefix hashPrefix : HashPrefix.values()) { - if (Arrays.equals(read, hashPrefix.bytes())) { - return hashPrefix; - } - } - return null; - } - - public STArray stArray() { - return STArray.fromParser(parser); - } - public Date rippleDate() { - return RippleDate.fromParser(parser); - } - - public BinaryParser parser() { - return parser; - } - - public TransactionResult readTransactionResult(UInt32 ledgerIndex) { - Hash256 hash = hash256(); - Transaction txn = (Transaction) vlStObject(); - TransactionMeta meta = (TransactionMeta) vlStObject(); - return new TransactionResult(ledgerIndex.longValue(), hash, txn, meta); - } - - public LedgerEntry readLE() { - Hash256 index = hash256(); - STObject object = vlStObject(); - LedgerEntry le = (LedgerEntry) object; - le.index(index); - return le; - } - - public int readOneInt() { - return parser.readOneInt(); - } - - public boolean end() { - return parser.end(); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/binary/STWriter.java b/ripple-core/src/main/java/com/ripple/core/binary/STWriter.java deleted file mode 100644 index 77f28eb7f7..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/binary/STWriter.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.ripple.core.binary; - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.serialized.BinarySerializer; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.StreamSink; -import com.ripple.core.types.known.sle.LedgerEntry; -import com.ripple.core.types.known.tx.result.TransactionResult; - -import java.io.*; - -public class STWriter implements BytesSink, Closeable { - BytesSink sink; - BinarySerializer serializer; - public STWriter(BytesSink bytesSink) { - serializer = new BinarySerializer(bytesSink); - sink = bytesSink; - } - - private OutputStream stream; - private STWriter(BytesSink sink, OutputStream stream) { - this(sink); - this.stream = stream; - } - - public static STWriter toFile(String path) { - try { - FileOutputStream fos = new FileOutputStream(path); - BufferedOutputStream bos = new BufferedOutputStream(fos); - return new STWriter(new StreamSink(bos), bos); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - } - - public void write(SerializedType obj) { - obj.toBytesSink(sink); - } - public void writeVl(SerializedType obj) { - serializer.addLengthEncoded(obj); - } - - @Override - public void add(byte aByte) { - sink.add(aByte); - } - - @Override - public void add(byte[] bytes) { - sink.add(bytes); - } - - public void write(TransactionResult result) { - write(result.hash); - writeVl(result.txn); - writeVl(result.meta); - } - - public void write(Hash256 hash256, LedgerEntry le) { - write(hash256); - writeVl(le); - } - - @Override - public void close() throws IOException { - if (this.stream != null) { - this.stream.close(); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/cache/SLECache.java b/ripple-core/src/main/java/com/ripple/core/cache/SLECache.java deleted file mode 100644 index 55552ede01..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/cache/SLECache.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.ripple.core.cache; - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.types.known.sle.LedgerEntry; -import com.ripple.core.types.known.tx.result.AffectedNode; -import com.ripple.core.types.known.tx.result.TransactionMeta; -import com.ripple.core.types.known.tx.result.TransactionResult; - -import java.util.TreeMap; - -public class SLECache { - private final TreeMap cache = new TreeMap(); - - public static class CacheEntry { - public LedgerEntry le; - public UInt32 prevTxnIndex; - public UInt32 prevLedger; - public boolean deleted = false; - - public void upateLedgerEntry(LedgerEntry le, UInt32 ledgerIndex, UInt32 txnIndex) { - if (doUpdate(txnIndex, ledgerIndex)) { - // in the first case - prevTxnIndex = txnIndex; - prevLedger = ledgerIndex; - - if (le == null) { - deleted = true; - } - this.le = le; - } - } - - private boolean doUpdate(UInt32 txnIndex, UInt32 ledgerIndex) { - if (le == null && !deleted) { - return true; - } - if (prevLedger == null) { - return true; - } - int ledgerCmp = ledgerIndex.compareTo(prevLedger); - if (ledgerCmp == 1) { - return true; - } - if (ledgerCmp == 0) { - if (prevTxnIndex == null) { - // We don't know, should log a warning or something - // Should we keep the first one that we have of this index - // or can we assume that the latest is the best? - return true; - } - if (txnIndex.compareTo(prevTxnIndex) == 1) { - // This happened AFTER - return true; - } - } - //ledgerCmp == -1 or txnIndex <= previousTxnIndex ss - return false; - } - } - - public boolean cache(LedgerEntry le, UInt32 validatedLedgerIndex) { - Hash256 index = le.ledgerIndex(); - CacheEntry ce = getOrCreate(index); - ce.upateLedgerEntry(le, validatedLedgerIndex, null); - return true; - } - - private CacheEntry getEntry(Hash256 index) { - return cache.get(index); - } - - private CacheEntry createEntry(Hash256 index) { - CacheEntry ce = new CacheEntry(); - cache.put(index, ce); - return ce; - } - - public LedgerEntry get(Hash256 index) { - CacheEntry entry = getEntry(index); - return entry == null || entry.deleted ? null : entry.le; - } - - public void updateFromTransactionResult(TransactionResult tr) { - if (!tr.validated) { - return; - } - - TransactionMeta meta = tr.meta; - UInt32 ledgerIndex = tr.ledgerIndex; - UInt32 txnIndex = meta.transactionIndex(); - - for (AffectedNode an : meta.affectedNodes()) { - Hash256 index = an.ledgerIndex(); - CacheEntry ce = getOrCreate(index); - ce.upateLedgerEntry(an.isDeletedNode() ? null : (LedgerEntry) an.nodeAsFinal(), - ledgerIndex, - txnIndex); - } - } - - private CacheEntry getOrCreate(Hash256 index) { - CacheEntry already = getEntry(index); - if (already == null) { - return createEntry(index); - } else { - return already; - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/AccountID.java b/ripple-core/src/main/java/com/ripple/core/coretypes/AccountID.java deleted file mode 100644 index 606c5b2b71..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/AccountID.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.ripple.core.coretypes; - -import com.ripple.core.coretypes.hash.Hash160; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.Index; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.fields.AccountIDField; -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.TypeTranslator; -import com.ripple.crypto.Seed; -import com.ripple.crypto.keys.IKeyPair; -import com.ripple.encodings.addresses.Addresses; -import com.ripple.encodings.common.B16; -import com.ripple.utils.Utils; - -/** - * Originally it was intended that AccountIDs would be variable length so that's - * why they are variable length encoded as top level field objects. - * - * Note however, that in practice, all account ids are just 160 bit hashes. - * Consider the fields TakerPaysIssuer and fixed length encoding of issuers in - * amount serializations. - * - * Thus, we extend Hash160 which affords us some functionality. - */ -public class AccountID extends Hash160 { - public static final AccountID NEUTRAL = fromInteger(1); - public static final AccountID XRP_ISSUER = fromInteger(0); - - public final String address; - - public AccountID(byte[] bytes) { - this(bytes, encodeAddress(bytes)); - } - - public AccountID(byte[] bytes, String address) { - super(bytes); - this.address = address; - } - - // Static from* constructors - public static AccountID fromString(String value) { - if (value.length() == 160 / 4) { - return fromBytes(B16.decode(value)); - } else { - return fromAddress(value); - } - } - - static public AccountID fromAddress(String address) { - byte[] bytes = Addresses.decodeAccountID(address); - return new AccountID(bytes, address); - } - - static public AccountID fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - - static public AccountID fromHex(String hex) { - return translate.fromHex(hex); - } - - public static AccountID fromKeyPair(IKeyPair kp) { - byte[] bytes = kp.id(); - return new AccountID(bytes, encodeAddress(bytes)); - } - - public static AccountID fromPassPhrase(String phrase) { - return fromKeyPair(Seed.fromPassPhrase(phrase).keyPair()); - } - - static public AccountID fromSeed(String seed) { - return fromKeyPair(Seed.getKeyPair(seed)); - } - - private static AccountID fromInteger(Integer n) { - return fromBytes(Utils.padTo160(new UInt32(n).toByteArray())); - } - - public static AccountID fromBytes(byte[] bytes) { - return new AccountID(bytes, encodeAddress(bytes)); - } - - @Override - public int hashCode() { - return address.hashCode(); - } - - @Override - public String toString() { - return address; - } - - public Issue issue(String code) { - return new Issue(Currency.fromString(code), this); - } - - public Issue issue(Currency c) { - return new Issue(c, this); - } - - public boolean isNativeIssuer() { - return this == XRP_ISSUER || equals(XRP_ISSUER); - } - - // SerializedType interface implementation - @Override - public Object toJSON() { - return toString(); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - to.add(bytes()); - } - - @Override - public Type type() { - return Type.AccountID; - } - - public Hash256 lineIndex(Issue issue) { - if (issue.isNative()) throw new AssertionError(); - return Index.rippleState(this, issue.issuer(), issue.currency()); - } - - public static class Translator extends TypeTranslator { - @Override - public AccountID fromParser(BinaryParser parser, Integer hint) { - if (hint == null) { - hint = 20; - } - return AccountID.fromBytes(parser.read(hint)); - } - - @Override - public String toString(AccountID obj) { - return obj.toString(); - } - - @Override - public AccountID fromString(String value) { - return AccountID.fromString(value); - } - } - - // - - static public Translator translate = new Translator(); - - // helpers - - private static String encodeAddress(byte[] address) { - return Addresses.encodeAccountID(address); - } - - // Typed field definitions - private static AccountIDField accountField(final Field f) { - return new AccountIDField() { - @Override - public Field getField() { - return f; - } - }; - } - - static public AccountIDField Account = accountField(Field.Account); - static public AccountIDField Owner = accountField(Field.Owner); - static public AccountIDField Destination = accountField(Field.Destination); - static public AccountIDField Issuer = accountField(Field.Issuer); - static public AccountIDField Target = accountField(Field.Target); - static public AccountIDField RegularKey = accountField(Field.RegularKey); - static public AccountIDField Authorize = accountField(Field.Authorize); - static public AccountIDField Unauthorize = accountField(Field.Unauthorize); -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/Amount.java b/ripple-core/src/main/java/com/ripple/core/coretypes/Amount.java deleted file mode 100644 index 3813a5ac09..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/Amount.java +++ /dev/null @@ -1,790 +0,0 @@ -package com.ripple.core.coretypes; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.fields.AmountField; -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import org.json.JSONObject; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.MathContext; -import java.math.RoundingMode; -import java.util.Objects; - -/** - * In ripple, amounts are either XRP, the native currency, or an IOU of - * a given currency as issued by a designated account. - */ -public class Amount extends Number implements SerializedType, Comparable - -{ - - private static BigDecimal TAKER_PAYS_FOR_THAT_DAMN_OFFER = new BigDecimal("1000000000000.000100"); - -// public static final Amount NEUTRAL_ZERO = new Amount(Currency.NEUTRAL, AccountID.NEUTRAL); - - /** - * Thrown when an Amount is constructed with an invalid value - */ - public static class PrecisionError extends RuntimeException { - public Amount illegal; - - public PrecisionError(String s) { - super(s); - } - - public PrecisionError(String s, Amount amount) { - super(s); - illegal = amount; - } - } - - // For rounding/multiplying/dividing - public static final MathContext MATH_CONTEXT = new MathContext(16, RoundingMode.HALF_UP); - // The maximum amount of digits in mantissa of an IOU amount - public static final int MAXIMUM_IOU_PRECISION = 16; - // The smallest quantity of an XRP is a drop, 1 millionth of an XRP - public static final int MAXIMUM_NATIVE_SCALE = 6; - // Defines bounds for native amounts - public static final BigDecimal MAX_NATIVE_VALUE = parseDecimal("100,000,000,000.0"); - public static final BigDecimal MIN_NATIVE_VALUE = parseDecimal("0.000,001"); - - // These are flags used when serializing to binary form - public static final UInt64 BINARY_FLAG_IS_IOU = new UInt64("8000000000000000", 16); - public static final UInt64 BINARY_FLAG_IS_NON_NEGATIVE_NATIVE = new UInt64("4000000000000000", 16); - - public static final Amount ONE_XRP = fromString("1.0"); - - // TODO, even though all these fields are effectively final, and only - // ever set it would be nice to actually use the final modifier. - // Perhaps create an amount builder. - - // The quantity of XRP or Issue(currency/issuer pairing) - // When native, the value unit is XRP, not drops. - private BigDecimal value; - private Currency currency; - // If the currency is XRP - private boolean isNative; - // Normally, in the constructor of an Amount the value is checked - // that it's scale/precision and quantity are correctly bounded. - // If unbounded is true, these checks are skipped. - // This is there for historical ledgers that contain amounts that - // would now be considered malformed (in the sense of the transaction - // engine result class temMALFORMED) - private boolean unbounded = false; - // The ZERO account is used for specifying the issuer for native - // amounts. In practice the issuer is never used when an - // amount is native. - private AccountID issuer; - - private UInt64 mantissa; - // The exponent is always calculated. - private int exponent; - - public Amount(BigDecimal value, Currency currency, AccountID issuer) { - this(value, currency, issuer, false); - } - - public Amount(BigDecimal xrpScaleValue) { - isNative = true; - currency = Currency.XRP; - this.setAndCheckValue(xrpScaleValue); - } - - public Amount(Number xrpScaleValue) { - isNative = true; - currency = Currency.XRP; - this.setAndCheckValue(BigDecimal.valueOf(xrpScaleValue.doubleValue())); - } - - public Amount(BigDecimal value, Currency currency, AccountID issuer, boolean isNative, boolean unbounded) { - this.isNative = isNative; - this.currency = currency; - this.unbounded = unbounded; - this.setAndCheckValue(value); - // done AFTER set value which sets some default values - this.issuer = issuer; - } - - public Amount(Currency currency, AccountID account) { - this(BigDecimal.ZERO, currency, account); - } - - // Private constructors - Amount(BigDecimal newValue, Currency currency, AccountID issuer, boolean isNative) { - this(newValue, currency, issuer, isNative, false); - } - - private Amount(BigDecimal value, String currency, String issuer) { - this(value, currency); - if (issuer != null) { - this.issuer = AccountID.fromString(issuer); - } - } - - public Amount(BigDecimal value, String currency) { - isNative = false; - this.currency = Currency.fromString(currency); - this.setAndCheckValue(value); - } - - private void setAndCheckValue(BigDecimal value) { - this.value = value.stripTrailingZeros(); - initialize(); - } - - private void initialize() { - if (isNative()) { - issuer = AccountID.XRP_ISSUER; - if (!unbounded) { - checkXRPBounds(); - } - // Offset is unused for native amounts - exponent = -6; // compared to drops. - } else { - issuer = AccountID.NEUTRAL; - exponent = calculateExponent(); - - if (value.precision() > MAXIMUM_IOU_PRECISION && !unbounded) { - String err = "value precision of " + value.precision() + - " is greater than maximum " + - "iou precision of " + MAXIMUM_IOU_PRECISION; - throw new PrecisionError(err, this); - } - } - mantissa = calculateMantissa(); - } - - private Amount newValue(BigDecimal newValue) { - return newValue(newValue, false, false); - } - - private Amount newValue(BigDecimal newValue, boolean round, boolean unbounded) { - if (round) { - newValue = roundValue(newValue, isNative); - } - return new Amount(newValue, currency, issuer, isNative, unbounded); - } - - private Amount newValue(BigDecimal val, boolean round) { - return newValue(val, round, false); - } - - /* Getters and Setters */ - - public BigDecimal value() { - return value; - } - - public Currency currency() { - return currency; - } - - public AccountID issuer() { - return issuer; - } - - public Issue issue() { - // TODO: store the currency and issuer as an Issue - return new Issue(currency, issuer); - } - - private UInt64 mantissa() { - // Having this lazily computed and then cached would give significant - // performance boosts in some single threaded contexts. Given the inputs - // for the method are at least effectively final, and the method is - // essentially a pure function, it seems probably safe to do. It - // seems at worst, that in a multi threaded context, some extra work - // will be done in rare cases? - // TODO: check this assumption - return mantissa; - } - - public int exponent() { - return exponent; - } - - public boolean isNative() { - return isNative; - } - - public String currencyString() { - return currency.toString(); - } - - public String issuerString() { - if (issuer == null) { - return ""; - } - return issuer.toString(); - } - - /* Offset & Mantissa Helpers */ - - /** - * @return a positive value for the mantissa - */ - private UInt64 calculateMantissa() { - if (isNative()) { - return new UInt64(bigIntegerDrops().abs()); - } else { - return new UInt64(bigIntegerIOUMantissa()); - } - } - - private int calculateExponent() { - return -MAXIMUM_IOU_PRECISION + value.precision() - value.scale(); - } - - private BigInteger bigIntegerIOUMantissa() { - return exactBigIntegerScaledByPowerOfTen(-exponent).abs(); - } - - private BigInteger bigIntegerDrops() { - return exactBigIntegerScaledByPowerOfTen(MAXIMUM_NATIVE_SCALE); - } - - private BigInteger exactBigIntegerScaledByPowerOfTen(int n) { - return value.scaleByPowerOfTen(n).toBigIntegerExact(); - } - - /* Equality testing */ - - private boolean equalValue(Amount amt) { - return compareTo(amt) == 0; - } - @Override - public boolean equals(Object obj) { - if (obj instanceof Amount) { - return equals((Amount) obj); - } - return super.equals(obj); - } - - public boolean equals(Amount amt) { - return equalValue(amt) && - currency.equals(amt.currency) && - (isNative() || issuer.equals(amt.issuer)); - } - - public boolean equalsExceptIssuer(Amount amt) { - return equalValue(amt) && - currencyString().equals(amt.currencyString()); - } - - public int compareTo(Amount amount) { - Objects.requireNonNull(amount); - return value.compareTo(amount.value); - } - - public boolean isZero() { - return value.signum() == 0; - } - - public boolean isNegative() { - return value.signum() == -1; - } - - // Maybe you want !isNegative() - // Any amount that !isNegative() isn't necessarily positive - // Is a zero amount strictly positive? no - public boolean isPositive() { - return value.signum() == 1; - } - - /** - - Arithmetic Operations - - There's no checking if an amount is of a different currency/issuer. - - All operations return amounts of the same currency/issuer as the - first operand. - - eg. - - amountOne.add(amountTwo) - - The currency/issuer of the resultant amount, is that of `amountOne` - - Divide and multiply are equivalent to the javascript ripple-lib - ratio_human and product_human. - - */ - public Amount add(BigDecimal augend) { - return newValue(value.add(augend), true); - } - - public Amount add(Amount augend) { - return add(augend.value); - } - - public Amount add(Number augend) { - return add(BigDecimal.valueOf(augend.doubleValue())); - } - - public Amount subtract(BigDecimal subtrahend) { - return newValue(value.subtract(subtrahend), true); - } - - public Amount subtract(Amount subtrahend) { - return subtract(subtrahend.value); - } - - public Amount subtract(Number subtrahend) { - return subtract(BigDecimal.valueOf(subtrahend.doubleValue())); - } - - public Amount multiply(BigDecimal divisor) { - return newValue(value.multiply(divisor, MATH_CONTEXT), true); - } - - public Amount multiply(Amount multiplicand) { - return multiply(multiplicand.value); - } - - public Amount multiply(Number multiplicand) { - return multiply(BigDecimal.valueOf(multiplicand.doubleValue())); - } - - public Amount divide(BigDecimal divisor) { - return newValue(value.divide(divisor, MATH_CONTEXT), true); - } - - public Amount divide(Amount divisor) { - return divide(divisor.value); - } - - public Amount divide(Number divisor) { - return divide(BigDecimal.valueOf(divisor.doubleValue())); - } - - public Amount negate() { - return newValue(value.negate()); - } - - public Amount abs() { - return newValue(value.abs()); - } - public Amount min(Amount val) { - return (compareTo(val) <= 0 ? this : val); - } - public Amount max(Amount val) { - return (compareTo(val) >= 0 ? this : val); - } - - /* Offer related helpers */ - public BigDecimal computeQuality(Amount toExchangeThisWith) { - return value.divide(toExchangeThisWith.value, MathContext.DECIMAL128); - } - /** - * @return Amount - * The real native unit is a drop, one million of which are an XRP. - * We want `one` unit at XRP scale (1e6 drops), or if it's an IOU, - * just `one`. - */ - public Amount one() { - if (isNative()) { - return ONE_XRP; - } else { - return issue().amount(1); - } - } - - /* Serialized Type implementation */ - - @Override - public Object toJSON() { - if (isNative()) { - return toDropsString(); - } else { - return toJSONObject(); - } - } - - public JSONObject toJSONObject() { - if (isNative()) { - throw new RuntimeException("Native amounts must be serialized as a string"); - } - - JSONObject out = new JSONObject(); - out.put("currency", currencyString()); - out.put("value", valueText()); - out.put("issuer", issuerString()); - return out; - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - // TODO: probably better off using long - UInt64 man = mantissa(); - - if (isNative()) { - if (!isNegative()) { - man = man.or(BINARY_FLAG_IS_NON_NEGATIVE_NATIVE); - } - to.add(man.toByteArray()); - } else { - int exponent = exponent(); - UInt64 packed; - - if (isZero()) { - packed = BINARY_FLAG_IS_IOU; - } else if (isNegative()) { - packed = man.or(new UInt64(512 + /* 0 + */ 97 + exponent).shiftLeft(64 - 10)); - } else { - packed = man.or(new UInt64(512 + 256 + 97 + exponent).shiftLeft(64 - 10)); - } - - to.add(packed.toByteArray()); - to.add(currency.bytes()); - to.add(issuer.bytes()); - } - } - - @Override - public Type type() { - return Type.Amount; - } - - public static class Translator extends TypeTranslator { - @Override - public Amount fromString(String s) { - // We need to use the full dotted.path here, otherwise - // we get confused with the AmountField Amount - return com.ripple.core.coretypes.Amount.fromString(s); - } - - @Override - public Amount fromParser(BinaryParser parser, Integer hint) { - BigDecimal value; - byte[] mantissa = parser.read(8); - byte b1 = mantissa[0], b2 = mantissa[1]; - - boolean isIOU = (b1 & 0x80) != 0; - boolean isPositive = (b1 & 0x40) != 0; - int sign = isPositive ? 1 : -1; - - if (isIOU) { - mantissa[0] = 0; - Currency curr = Currency.fromParser(parser); - AccountID issuer = AccountID.fromParser(parser); - int exponent = ((b1 & 0x3F) << 2) + ((b2 & 0xff) >> 6) - 97; - mantissa[1] &= 0x3F; - - value = new BigDecimal(new BigInteger(sign, mantissa), -exponent); - return new Amount(value, curr, issuer, false); - } else { - mantissa[0] &= 0x3F; - value = xrpFromDropsMantissa(mantissa, sign); - return new Amount(value); - } - } - - @Override - public String toString(Amount obj) { - return obj.stringRepr(); - } - - public JSONObject toJSONObject(Amount obj) { - return obj.toJSONObject(); - } - - @Override - public Amount fromJSONObject(JSONObject jsonObject) { - String valueString = jsonObject.getString("value"); - String issuerString = jsonObject.getString("issuer"); - String currencyString = jsonObject.getString("currency"); - return new Amount(new BigDecimal(valueString), currencyString, issuerString); - } - - @Override - public Amount fromJacksonObject(ObjectNode object) { - checkField(object, "value"); - checkField(object, "issuer"); - checkField(object, "currency"); - String value = object.get("value").asText(); - String issuer = object.get("issuer").asText(); - String currency = object.get("currency").asText(); - return new Amount(new BigDecimal(value), currency, issuer); - } - - private void checkField(ObjectNode object, String field) { - if (!object.has(field)) { - throw new IllegalArgumentException(object + "is missing `" + - field + "`"); - } - } - } - static public Translator translate = new Translator(); - - public static BigDecimal xrpFromDropsMantissa(byte[] mantissa, int sign) { - return new BigDecimal(new BigInteger(sign, mantissa), 6); - } - - /* Number overides */ - @Override - public int intValue() { - return value.intValueExact(); - } - - @Override - public long longValue() { - return value.longValueExact(); - } - - @Override - public float floatValue() { - return value.floatValue(); - } - - @Override - public double doubleValue() { - return value.doubleValue(); - } - - public BigInteger bigIntegerValue() { - return value.toBigIntegerExact(); - } - - public Amount newIssuer(AccountID issuer) { - return new Amount(value, currency, issuer, isNative, unbounded); - } - - public Amount copy() { - return new Amount(value, currency, issuer, isNative, unbounded); - } - - // Static constructors - public static Amount fromString(String val) { - if (val.contains("/")) { - return fromIOUString(val); - } else if (val.contains(".")) { - return fromXrpString(val); - } else { - return fromDropString(val); - } - - } - - public static Amount fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - - public static Amount fromHex(String hex) { - return translate.fromHex(hex); - } - public static Amount fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - public static Amount fromJSONObject(JSONObject jsonObject) { - return translate.fromJSONObject(jsonObject); - } - - public static Amount fromJacksonObject(ObjectNode object) { - return translate.fromJacksonObject(object); - } - - public static Amount fromValue(Object amount) { - return translate.fromValue(amount); - } - - public static Amount fromDropString(String val) { - BigDecimal xrp = new BigDecimal(val).scaleByPowerOfTen(-6); - checkDropsValueWhole(val); - return new Amount(xrp); - } - - public static Amount fromIOUString(String val) { - String[] split = val.split("/"); - if (split.length == 1) { - throw new RuntimeException("IOU string must be in the form " + - "number/currencyString or " + - "number/currencyString/issuerString"); - } else if (split.length == 2) { - return new Amount(new BigDecimal(split[0]), split[1]); - } else { - return new Amount(new BigDecimal(split[0]), split[1], split[2]); - } - } - - @Deprecated - private static Amount fromXrpString(String valueString) { - BigDecimal val = new BigDecimal(valueString); - return new Amount(val); - } - - /** - * @return A String representation as used by ripple json format - */ - public String stringRepr() { - if (isNative()) { - return toDropsString(); - } else { - return iouTextFull(); - } - } - - public String toDropsString() { - if (!isNative()) { - throw new RuntimeException("Amount is not native"); - } - return bigIntegerDrops().toString(); - } - - private String iouText() { - return String.format("%s/%s", valueText(), currencyString()); - } - - public String iouTextFull() { - return String.format("%s/%s/%s", valueText(), currencyString(), issuerString()); - } - - public String toTextFull() { - if (isNative()) { - return nativeText(); - } else { - return iouTextFull(); - } - } - - public String nativeText() { - return String.format("%s/XRP", valueText()); - } - - @Override - public String toString() { - return toTextFull(); - } - - public String toText() { - if (isNative()) { - return nativeText(); - } else { - return iouText(); - } - } - - /** - * @return A String containing the value (in XRP scale when native) - * as a decimal number - * - */ - public String valueText() { - return value.signum() == 0 ? "0" : value().toPlainString(); - } - - private void checkLowerDropBound(BigDecimal val) { - if (val.scale() > 6) { - PrecisionError bigger = getOutOfBoundsError(val, - "smaller than min native value", - MIN_NATIVE_VALUE); - bigger.illegal = this; - throw bigger; - } - } - - private void checkUpperBound(BigDecimal val) { - if (val.compareTo(MAX_NATIVE_VALUE) > 0) { - PrecisionError bigger = getOutOfBoundsError(val, - "bigger than max native value ", - MAX_NATIVE_VALUE); - bigger.illegal = this; - throw bigger; - } - } - - private static PrecisionError getOutOfBoundsError( - BigDecimal abs, - String sized, - BigDecimal bound) { - return new PrecisionError( - abs.toPlainString() + " absolute XRP is " + sized + bound); - } - - private void checkXRPBounds() { - BigDecimal v = value.abs(); - try { - checkLowerDropBound(v); - checkUpperBound(v); - } catch (PrecisionError e) { - if (v.compareTo(TAKER_PAYS_FOR_THAT_DAMN_OFFER) == 0) { - return; - } - throw e; - } - } - - - private static int significantDigits(BigDecimal input) { - input = input.stripTrailingZeros(); - return input.scale() < 0 - ? input.precision() - input.scale() - : input.precision(); - } - - public int significantDigits() { - return significantDigits(value); - } - - public static void checkDropsValueWhole(String drops) { - boolean contains = drops.contains("."); - if (contains) { - throw new RuntimeException("Drops string contains floating point is decimal"); - } - } - - public static BigDecimal roundValue(BigDecimal value, boolean nativeSrc) { - int i = value.precision() - value.scale(); - return value.setScale(nativeSrc ? MAXIMUM_NATIVE_SCALE : - MAXIMUM_IOU_PRECISION - i, - MATH_CONTEXT.getRoundingMode()); - } - - private static BigDecimal parseDecimal(String s) { - return new BigDecimal(s.replace(",", "")); //# .scaleByPowerOfTen(6); - } - - private static AmountField amountField(final Field f) { - return new AmountField() { - @Override - public Field getField() { - return f; - } - }; - } - - static public AmountField Amount = amountField(Field.Amount); - static public AmountField Balance = amountField(Field.Balance); - static public AmountField LimitAmount = amountField(Field.LimitAmount); - static public AmountField DeliveredAmount = amountField(Field.DeliveredAmount); - static public AmountField TakerPays = amountField(Field.TakerPays); - static public AmountField TakerGets = amountField(Field.TakerGets); - static public AmountField LowLimit = amountField(Field.LowLimit); - static public AmountField HighLimit = amountField(Field.HighLimit); - static public AmountField Fee = amountField(Field.Fee); - static public AmountField SendMax = amountField(Field.SendMax); - static public AmountField DeliverMin = amountField(Field.DeliverMin); - static public AmountField MinimumOffer = amountField(Field.MinimumOffer); - static public AmountField RippleEscrow = amountField(Field.RippleEscrow); - static public AmountField taker_gets_funded = amountField(Field.taker_gets_funded); - static public AmountField taker_pays_funded = amountField(Field.taker_pays_funded); - -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/Blob.java b/ripple-core/src/main/java/com/ripple/core/coretypes/Blob.java deleted file mode 100644 index 48708f78f5..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/Blob.java +++ /dev/null @@ -1,119 +0,0 @@ - -package com.ripple.core.coretypes; - -import com.ripple.core.fields.BlobField; -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import com.ripple.encodings.common.B16; -import org.bouncycastle.util.encoders.Hex; - -public class Blob implements SerializedType { - public Blob(byte[] bytes) { - buffer = bytes.clone(); - } - - private final byte[] buffer; - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return buffer; - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - @Override - public Type type() { - return Type.Blob; - } - - public static Blob fromBytes(byte[] bytes) { - return new Blob(bytes); - } - - public static Blob fromHex(String hex) { - return fromBytes(B16.decode(hex)); - } - - public static Blob fromParser(BinaryParser parser, int hint) { - return translate.fromParser(parser, hint); - } - public static Blob fromParser(BinaryParser parser) { - return translate.fromParser(parser, null); - } - - public static class Translator extends TypeTranslator { - @Override - public Blob fromParser(BinaryParser parser, Integer hint) { - if (hint == null) { - hint = parser.size() - parser.pos(); - } - return new Blob(parser.read(hint)); - } - - @Override - public Object toJSON(Blob obj) { - return toString(obj); - } - - @Override - public String toString(Blob obj) { - return B16.encode(obj.buffer); - } - - @Override - public Blob fromString(String value) { - return new Blob(Hex.decode(value)); - } - - @Override - public void toBytesSink(Blob obj, BytesSink to) { - to.add(obj.buffer.clone()); - } - } - - static public Translator translate = new Translator(); - - private static BlobField blobField(final Field f) { - return new BlobField() { - @Override - public Field getField() { - return f; - } - }; - } - - static public BlobField PublicKey = blobField(Field.PublicKey); - static public BlobField MessageKey = blobField(Field.MessageKey); - static public BlobField SigningPubKey = blobField(Field.SigningPubKey); - static public BlobField TxnSignature = blobField(Field.TxnSignature); - static public BlobField MasterSignature = blobField(Field.MasterSignature); - static public BlobField Signature = blobField(Field.Signature); - static public BlobField Domain = blobField(Field.Domain); - static public BlobField FundCode = blobField(Field.FundCode); - static public BlobField RemoveCode = blobField(Field.RemoveCode); - static public BlobField ExpireCode = blobField(Field.ExpireCode); - static public BlobField CreateCode = blobField(Field.CreateCode); - - static public BlobField MemoType = blobField(Field.MemoType); - static public BlobField MemoData = blobField(Field.MemoData); - static public BlobField MemoFormat = blobField(Field.MemoFormat); - static public BlobField Condition = blobField(Field.Condition); - static public BlobField Fulfillment = blobField(Field.Fulfillment); -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/Currency.java b/ripple-core/src/main/java/com/ripple/core/coretypes/Currency.java deleted file mode 100644 index 27832e335b..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/Currency.java +++ /dev/null @@ -1,250 +0,0 @@ -package com.ripple.core.coretypes; - -import com.ripple.core.coretypes.hash.Hash160; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.encodings.common.B16; -import com.ripple.utils.Utils; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.MathContext; -import java.util.Date; -import java.util.concurrent.TimeUnit; - -/** - * Funnily enough, yes, in rippled a currency is represented by a Hash160 type. - * For the sake of consistency and convenience, this quirk is repeated here. - * - * https://gist.github.com/justmoon/8597643 - */ -public class Currency extends Hash160 { - public static final Currency NEUTRAL = new Currency(Utils.padTo160(BigInteger.ONE.toByteArray())); - public static final Currency XRP = new Currency(Utils.padTo160(BigInteger.ZERO.toByteArray())); - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - public boolean isNative() { - return this == Currency.XRP || equals(Currency.XRP); - } - - public boolean isIOU() { - return !isNative(); - } - - public static enum Type { - HASH, - ISO, // three letter isoCode - DEMURRAGE, - UNKNOWN; - - public static Type fromByte(byte typeByte) { - if (typeByte == 0x00) { - return ISO; - } else if (typeByte == 0x01) { - return DEMURRAGE; - } else if ((typeByte & 0x80) != 0) { - return HASH; - } else { - return UNKNOWN; - } - } - } - Type type; - - public static class Demurrage { - Date interestStart; - String isoCode; - double interestRate; - - static public BigDecimal applyRate(BigDecimal amount, BigDecimal rate, TimeUnit time, long units) { - BigDecimal appliedRate = getSeconds(time, units).divide(rate, MathContext.DECIMAL64); - BigDecimal factor = BigDecimal.valueOf(Math.exp(appliedRate.doubleValue())); - return amount.multiply(factor, MathContext.DECIMAL64); - } - - static public BigDecimal calculateRate(BigDecimal rate, TimeUnit time, long units) { - BigDecimal seconds = getSeconds(time, units); - BigDecimal log = ln(rate); - return seconds.divide(log, MathContext.DECIMAL64); - } - - private static BigDecimal ln(BigDecimal bd) { - return BigDecimal.valueOf(Math.log(bd.doubleValue())); - } - - private static BigDecimal getSeconds(TimeUnit time, long units) { - return BigDecimal.valueOf(time.toSeconds(units)); - } - - public Demurrage(byte[] bytes) { - BinaryParser parser = new BinaryParser(bytes); - parser.skip(1); // The type - isoCode = isoCodeFromBytesAndOffset(parser.read(3), 0);// The isoCode - interestStart = RippleDate.fromParser(parser); - long l = UInt64.fromParser(parser).longValue(); - interestRate = Double.longBitsToDouble(l); - } - } - public Demurrage demurrage = null; - public Currency(byte[] bytes) { - super(bytes); - type = Type.fromByte(this.hash[0]); - if (type == Type.DEMURRAGE) { - demurrage = new Demurrage(bytes); - } - } - - /** - * It's better to extend HashTranslator than the Hash160.Translator directly - * That way the generics can still vibe with the @Override - */ - private static class CurrencyTranslator extends HashTranslator { - @Override - public int byteWidth() { - return 20; - } - - @Override - public Currency newInstance(byte[] b) { - return new Currency(b); - } - - @Override - public Object toJSON(Currency obj) { - return obj.toString(); - } - - @Override - public Currency fromString(String value) { - if (value.length() == 40 /* byteWidth() * 2 */) { - return newInstance(B16.decode(value)); - } else if (value.equals("XRP")) { - return XRP; - } else { - if (!(value.length() == 3)) { -// if (!value.matches("[A-Z0-9]{3}")) { - throw new RuntimeException("Currency code must be 3 characters"); - } - return newInstance(encodeCurrency(value)); - } - } - } - - public static Currency fromString(String currency) { - return translate.fromString(currency); - } - - public static Currency fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - - @Override - public String toString() { - switch (type) { - case ISO: - String code = getCurrencyCodeFromTLCBytes(bytes()); - if (code.equals("XRP")) { - // HEX of the bytes - return super.toString(); - } else if (code.equals("\0\0\0")) { - return "XRP"; - } else { - // the 3 letter isoCode - return code; - } - case HASH: - case DEMURRAGE: - case UNKNOWN: - default: - return super.toString(); - } - } - - public String humanCode() { - if (type == Type.ISO) { - return getCurrencyCodeFromTLCBytes(hash); - } else if (type == Type.DEMURRAGE) { - return isoCodeFromBytesAndOffset(hash, 1); - } else { - throw new IllegalStateException("No human code for currency of type " + type); - } - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Currency) { - Currency other = (Currency) obj; - byte[] bytes = this.bytes(); - byte[] otherBytes = other.bytes(); - - if (type == Type.ISO && other.type == Type.ISO) { - return (bytes[12] == otherBytes[12] && - bytes[13] == otherBytes[13] && - bytes[14] == otherBytes[14]); - } - } - return super.equals(obj); // Full comparison - } - - private static CurrencyTranslator translate = new CurrencyTranslator(); - - /* - * The following are static methods, legacy from when there was no - * usage of Currency objects, just String with "XRP" ambiguity. - * */ - public static byte[] encodeCurrency(String currencyCode) { - byte[] currencyBytes = new byte[20]; - currencyBytes[12] = (byte) currencyCode.codePointAt(0); - currencyBytes[13] = (byte) currencyCode.codePointAt(1); - currencyBytes[14] = (byte) currencyCode.codePointAt(2); - return currencyBytes; - } - - public static String getCurrencyCodeFromTLCBytes(byte[] bytes) { - int i; - boolean zeroInNonCurrencyBytes = true; - - for (i = 0; i < 20; i++) { - zeroInNonCurrencyBytes = zeroInNonCurrencyBytes && - ((i == 12 || i == 13 || i == 14) || // currency bytes (0 or any other) - bytes[i] == 0); // non currency bytes (0) - } - - if (zeroInNonCurrencyBytes) { - return isoCodeFromBytesAndOffset(bytes, 12); - } else { - throw new IllegalStateException("Currency is invalid"); - } - } - - private static char charFrom(byte[] bytes, int i) { - return (char) bytes[i]; - } - - private static String isoCodeFromBytesAndOffset(byte[] bytes, int offset) { - char a = charFrom(bytes, offset); - char b = charFrom(bytes, offset + 1); - char c = charFrom(bytes, offset + 2); - return "" + a + b + c; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/Flags.java b/ripple-core/src/main/java/com/ripple/core/coretypes/Flags.java deleted file mode 100644 index 9b0c319dcf..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/Flags.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.ripple.core.coretypes; - -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; - -import java.util.BitSet; - -// TODO -public class Flags extends BitSet implements SerializedType { - @Override - public Object toJSON() { - return null; - } - - @Override - public byte[] toBytes() { - return new byte[0]; - } - - @Override - public String toHex() { - return null; - } - - @Override - public void toBytesSink(BytesSink to) { - - } - - @Override - public Type type() { - return Type.UInt32; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/Issue.java b/ripple-core/src/main/java/com/ripple/core/coretypes/Issue.java deleted file mode 100644 index 44f75dae82..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/Issue.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.ripple.core.coretypes; - -import com.ripple.core.coretypes.hash.Hash160; -import org.json.JSONObject; - -import java.math.BigDecimal; - -/** - * Represents a currency/issuer pair - */ -public class Issue implements Comparable { - - public static final Issue XRP = fromString("XRP"); - final Currency currency; - final AccountID issuer; - - public Issue(Currency currency, AccountID issuer) { - this.currency = currency; - this.issuer = issuer; - } - - public static Issue fromString(String pair) { - String[] split = pair.split("/"); - return fromStringPair(split); - } - - private static Issue fromStringPair(String[] split) { - if (split.length == 2) { - return new Issue(Currency.fromString(split[0]), AccountID.fromString(split[1])); - } else if (split[0].equals("XRP")) { - return new Issue(Currency.XRP, AccountID.XRP_ISSUER); - } else { - throw new RuntimeException("Issue string must be XRP or $currency/$issuer"); - } - } - - /** - * See {@link com.ripple.core.fields.Field#TakerGetsCurrency} - * See {@link com.ripple.core.fields.Field#TakerGetsIssuer} - * - * TODO: better handling of Taker(Gets|Pays)(Issuer|Curency) - * maybe special subclasses of AccountID / Currency - * respectively? - */ - public static Issue from160s(Hash160 currency, Hash160 issuer) { - return new Issue(new Currency(currency.bytes()), - new AccountID(issuer.toBytes())); - } - - public Currency currency() { - return currency; - } - - public AccountID issuer() { - return issuer; - } - - @Override - public String toString() { - if (isNative()) { - return "XRP"; - } else { - return String.format("%s/%s", currency, issuer); - } - } - - public JSONObject toJSON() { - JSONObject o = new JSONObject(); - o.put("currency", currency); - if (!isNative()) { - o.put("issuer", issuer); - } - return o; - } - - public Amount amount(BigDecimal value) { - return new Amount(value, currency, issuer, isNative()); - } - - public boolean isNative() { - return this == XRP || currency.equals(Currency.XRP); - } - - public Amount amount(Number value) { - return new Amount(BigDecimal.valueOf(value.doubleValue()), currency, issuer, isNative()); - } - - @Override - public int compareTo(Issue o) { - int ret = issuer.compareTo(o.issuer); - if (ret != 0) { - return ret; - } - ret = currency.compareTo(o.currency); - return ret; - } - - public Amount roundedAmount(BigDecimal amount) { - return amount(Amount.roundValue(amount, isNative())); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/IssuePair.java b/ripple-core/src/main/java/com/ripple/core/coretypes/IssuePair.java deleted file mode 100644 index 0196e67f86..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/IssuePair.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.ripple.core.coretypes; - -import org.json.JSONObject; - -import java.text.MessageFormat; - -public class IssuePair implements Comparable { - public final Issue pays; - public final Issue gets; - - public IssuePair(Issue pays, Issue gets) { - this.pays = pays; - this.gets = gets; - } - - @Override - public String toString() { - return MessageFormat.format("{0}/{1}", pays, gets); - } - - @Override - public int compareTo(IssuePair o) { - int cmp = pays.compareTo(o.pays); - if (cmp == 0) { - cmp = gets.compareTo(o.gets); - } - return cmp; - } - - public JSONObject toJSON() { - JSONObject ret = new JSONObject(); - ret.put("taker_gets", gets.toJSON()); - ret.put("taker_pays", pays.toJSON()); - return ret; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/PathSet.java b/ripple-core/src/main/java/com/ripple/core/coretypes/PathSet.java deleted file mode 100644 index b054229787..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/PathSet.java +++ /dev/null @@ -1,273 +0,0 @@ -package com.ripple.core.coretypes; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ripple.core.fields.Field; -import com.ripple.core.fields.PathSetField; -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.util.ArrayList; - -public class PathSet extends ArrayList implements SerializedType { - public static byte PATH_SEPARATOR_BYTE = (byte) 0xFF; - public static byte PATHSET_END_BYTE = (byte) 0x00; - - public PathSet(){} - - public static PathSet fromJSONArray(JSONArray array) { - return translate.fromJSONArray(array); - } - - public static PathSet fromHex(String hex) { - return translate.fromHex(hex); - } - - public static class Hop { - public static byte TYPE_ACCOUNT = (byte) 0x01; - public static byte TYPE_CURRENCY = (byte) 0x10; - public static byte TYPE_ISSUER = (byte) 0x20; - public static final int TYPE_ACCOUNT_CURRENCY_ISSUER = TYPE_CURRENCY | TYPE_ACCOUNT | TYPE_ISSUER; - public static final int TYPE_ACCOUNT_CURRENCY = TYPE_CURRENCY | TYPE_ACCOUNT; - public static int VALID_TYPE_MASK = ~(TYPE_ACCOUNT | TYPE_CURRENCY | TYPE_ISSUER); - - public AccountID account; - public AccountID issuer; - public Currency currency; - private int type; - - public boolean hasIssuer() { - return issuer != null; - } - public boolean hasCurrency() { - return currency != null; - } - public boolean hasAccount() { - return account != null; - } - - public int getType() { - if (type == 0) { - synthesizeType(); - } - return type; - } - - static public Hop fromJSONObject(JSONObject json) { - Hop hop = new Hop(); - if (json.has("account")) { - hop.account = AccountID.fromAddress(json.getString("account")); - } - if (json.has("issuer")) { - hop.issuer = AccountID.fromAddress(json.getString("issuer")); - } - if (json.has("currency")) { - hop.currency = Currency.fromString(json.getString("currency")); - } - if (json.has("type")) { - hop.type = json.getInt("type"); - } - return hop; - } - - public void synthesizeType() { - type = 0; - - if (hasAccount()) type |= TYPE_ACCOUNT; - if (hasCurrency()) type |= TYPE_CURRENCY; - if (hasIssuer()) type |= TYPE_ISSUER; - } - - public JSONObject toJSONObject() { - JSONObject object = new JSONObject(); - object.put("type", getType()); - - if (hasAccount()) object.put("account", account.toJSON()); - if (hasIssuer()) object.put("issuer", issuer.toJSON()); - if (hasCurrency()) object.put("currency", currency.toJSON()); - return object; - } - - public static Hop fromJacksonObject(ObjectNode json) { - Hop hop = new Hop(); - if (json.has("account")) { - hop.account = AccountID.fromAddress(json.get("account").asText()); - } - if (json.has("issuer")) { - hop.issuer = AccountID.fromAddress(json.get("issuer").asText()); - } - if (json.has("currency")) { - hop.currency = Currency.fromString(json.get("currency").asText()); - } - if (json.has("type")) { - hop.type = json.get("type").asInt(); - } - return hop; - - } - } - public static class Path extends ArrayList { - static public Path fromJSONArray(JSONArray array) { - Path path = new Path(); - int nHops = array.length(); - for (int i = 0; i < nHops; i++) { - JSONObject hop = array.getJSONObject(i); - path.add(Hop.fromJSONObject(hop)); - } - - return path; - } - static public Path fromJacksonArray(ArrayNode array) { - Path path = new Path(); - int nHops = array.size(); - for (int i = 0; i < nHops; i++) { - ObjectNode hop = (ObjectNode) array.get(i); - path.add(Hop.fromJacksonObject(hop)); - } - return path; - } - public JSONArray toJSONArray() { - JSONArray array = new JSONArray(); - for (Hop hop : this) { - array.put(hop.toJSONObject()); - } - return array; - } - } - - public JSONArray toJSONArray() { - JSONArray array = new JSONArray(); - for (Path path : this) { - array.put(path.toJSONArray()); - } - return array; - } - - // SerializedType interface implementation - @Override - public Object toJSON() { - return toJSONArray(); - } - - @Override - public void toBytesSink(BytesSink buffer) { - int n = 0; - for (Path path : this) { - if (n++ != 0) { - buffer.add(PATH_SEPARATOR_BYTE); - } - for (Hop hop : path) { - int type = hop.getType(); - buffer.add((byte) type); - if (hop.hasAccount()) { - buffer.add(hop.account.bytes()); - } - if (hop.hasCurrency()) { - buffer.add(hop.currency.bytes()); - } - if (hop.hasIssuer()) { - buffer.add(hop.issuer.bytes()); - } - } - } - buffer.add(PATHSET_END_BYTE); - } - - @Override - public Type type() { - return Type.PathSet; - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - - public static class Translator extends TypeTranslator { - @Override - public PathSet fromParser(BinaryParser parser, Integer hint) { - PathSet pathSet = new PathSet(); - PathSet.Path path = null; - while (!parser.end()) { - byte type = parser.readOne(); - if (type == PATHSET_END_BYTE) { - break; - } - if (path == null) { - path = new PathSet.Path(); - pathSet.add(path); - } - if (type == PATH_SEPARATOR_BYTE) { - path = null; - continue; - } - - PathSet.Hop hop = new PathSet.Hop(); - path.add(hop); - if ((type & Hop.TYPE_ACCOUNT) != 0) { - hop.account = AccountID.fromParser(parser); - } - if ((type & Hop.TYPE_CURRENCY) != 0) { - hop.currency = Currency.fromParser(parser); - } - if ((type & Hop.TYPE_ISSUER) != 0) { - hop.issuer = AccountID.fromParser(parser); - } - } - - return pathSet; - } - - @Override - public PathSet fromJSONArray(JSONArray array) { - PathSet paths = new PathSet(); - - int nPaths = array.length(); - - for (int i = 0; i < nPaths; i++) { - JSONArray path = array.getJSONArray(i); - paths.add(Path.fromJSONArray(path)); - } - return paths; - - } - - @Override - public PathSet fromJacksonArray(ArrayNode array) { - PathSet paths = new PathSet(); - - int nPaths = array.size(); - - for (int i = 0; i < nPaths; i++) { - ArrayNode path = (ArrayNode) array.get(i); - paths.add(Path.fromJacksonArray(path)); - } - - return paths; - } - } - static public Translator translate = new Translator(); - - public static PathSet fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - public static PathSet fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - private static PathSetField pathsetField(final Field f) { - return new PathSetField(){ @Override public Field getField() {return f;}}; - } - static public PathSetField Paths = pathsetField(Field.Paths); -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/Quality.java b/ripple-core/src/main/java/com/ripple/core/coretypes/Quality.java deleted file mode 100644 index 212da6d78e..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/Quality.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.ripple.core.coretypes; - -import com.ripple.core.coretypes.hash.Hash256; - -import java.math.BigDecimal; -import java.math.BigInteger; - -public class Quality { - /** - * Finds the quality (TakerPays/TakerGets) ratio packed into the last 64 - * bits of root DirectoryNode ledger indexes. - */ - public static BigDecimal fromBookDirectory(Hash256 bookDirectory, - boolean payIsNative, - boolean getIsNative) { - // The last 7 bytes contains the mantissa - byte[] mantissa = bookDirectory.slice(-7); - // Most significant byte has the exponent packed - int exponent = ( bookDirectory.get(-8) & 0xFF) - 100; - // Return the value in XRP scale, rather than drops, as stored. - int scale = -(payIsNative ? exponent - 6 : - getIsNative ? exponent + 6 : exponent); - BigInteger unsignedBig = new BigInteger(1, mantissa); - return new BigDecimal(unsignedBig, scale); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/RippleDate.java b/ripple-core/src/main/java/com/ripple/core/coretypes/RippleDate.java deleted file mode 100644 index 71a94e1ce9..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/RippleDate.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.ripple.core.coretypes; - -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.BinaryParser; - -import java.text.SimpleDateFormat; -import java.util.*; - -//public class RippleDate extends Date implements SerializedType { -public class RippleDate extends Date { - private static final SimpleDateFormat sdf = new SimpleDateFormat(); - - public static long RIPPLE_EPOCH_SECONDS_OFFSET = 0x386D4380; - static { - sdf.setTimeZone(new SimpleTimeZone(0, "GMT")); - sdf.applyPattern("dd MMM yyyy HH:mm:ss z"); - - /** - * Magic constant tested and documented. - * - * Seconds since the unix epoch from unix time (accounting leap years etc) - * at 1/January/2000 GMT - */ - GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); - cal.set(2000, Calendar.JANUARY, 1, 0, 0, 0); - long computed = cal.getTimeInMillis() / 1000; - assertEquals("01 Jan 2000 00:00:00 GMT", sdf.format(cal.getTime())); // TODO - assertEquals(RippleDate.RIPPLE_EPOCH_SECONDS_OFFSET, computed); - } - - static public String gmtString(Date date) { - return sdf.format(date); - } - - private static void assertEquals(String s, String s1) { - if (!s.equals(s1)) throw new AssertionError(String.format("%s != %s", s, s1)); - } - private static void assertEquals(long a, long b) { - if (a != b) throw new AssertionError(String.format("%s != %s", a, b)); - } - - private RippleDate() { - super(); - } - private RippleDate(long milliseconds) { - super(milliseconds); - } - - public long secondsSinceRippleEpoch() { - return ((this.getTime() / 1000) - RIPPLE_EPOCH_SECONDS_OFFSET); - } - public static RippleDate fromSecondsSinceRippleEpoch(Number seconds) { - return new RippleDate((seconds.longValue() + RIPPLE_EPOCH_SECONDS_OFFSET) * 1000); - } - public static RippleDate fromParser(BinaryParser parser) { - UInt32 uInt32 = UInt32.fromParser(parser); - return fromSecondsSinceRippleEpoch(uInt32); - } - public static RippleDate now() { - return new RippleDate(); - } - -/* @Override - public Object toJSON() { - return secondsSinceRippleEpoch(); - } - - @Override - public byte[] toBytes() { - return new UInt32(secondsSinceRippleEpoch()).toBytes(); - } - - @Override - public String toHex() { - return new UInt32(secondsSinceRippleEpoch()).toHex(); - } - - @Override - public void toBytesSink(BytesSink to) { - new UInt32(secondsSinceRippleEpoch()).toBytesSink(to); - }*/ -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/STArray.java b/ripple-core/src/main/java/com/ripple/core/coretypes/STArray.java deleted file mode 100644 index 6e7b0191f3..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/STArray.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.ripple.core.coretypes; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ripple.core.fields.Field; -import com.ripple.core.fields.STArrayField; -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.util.ArrayList; - -public class STArray extends ArrayList implements SerializedType { - - public JSONArray toJSONArray() { - JSONArray array = new JSONArray(); - - for (STObject so : this) { - array.put(so.toJSON()); - } - - return array; - } - - @Override - public Object toJSON() { - return toJSONArray(); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - for (STObject stObject : this) { - stObject.toBytesSink(to); - } - } - - @Override - public Type type() { - return Type.STArray; - } - - public static STArray fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - public static STArray fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - public static STArray fromHex(String hex) { - return translate.fromHex(hex); - } - - public static class Translator extends TypeTranslator { - @Override - public STArray fromParser(BinaryParser parser, Integer hint) { - STArray stArray = new STArray(); - while (!parser.end()) { - Field field = parser.readField(); - if (field == Field.ArrayEndMarker) { - break; - } - STObject outer = new STObject(); - // assert field.getType() == Type.STObject; - outer.put(field, STObject.fromParser(parser)); - stArray.add(STObject.formatted(outer)); - } - return stArray; - } - - public JSONArray toJSONArray(STArray obj) { - return obj.toJSONArray(); - } - - @Override - public STArray fromJSONArray(JSONArray jsonArray) { - STArray arr = new STArray(); - - for (int i = 0; i < jsonArray.length(); i++) { - Object o = jsonArray.get(i); - arr.add(STObject.fromJSONObject((JSONObject) o)); - } - - return arr; - } - @Override - public STArray fromJacksonArray(ArrayNode jsonArray) { - STArray arr = new STArray(); - - for (int i = 0; i < jsonArray.size(); i++) { - ObjectNode object = (ObjectNode) jsonArray.get(i); - arr.add(STObject.fromJacksonObject(object)); - } - - return arr; - } - } - static public Translator translate = new Translator(); - - public STArray(){} - - private static STArrayField starrayField(final Field f) { - return new STArrayField(){ @Override public Field getField() {return f;}}; - } - - static public STArrayField AffectedNodes = starrayField(Field.AffectedNodes); - static public STArrayField SignerEntries = starrayField(Field.SignerEntries); - static public STArrayField Signers = starrayField(Field.Signers); - - static public STArrayField Template = starrayField(Field.Template); - static public STArrayField Necessary = starrayField(Field.Necessary); - static public STArrayField Sufficient = starrayField(Field.Sufficient); - static public STArrayField Majorities = starrayField(Field.Majorities); - static public STArrayField Memos = starrayField(Field.Memos); - static public STArrayField ArrayEndMarker = starrayField(Field.ArrayEndMarker); -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/STObject.java b/ripple-core/src/main/java/com/ripple/core/coretypes/STObject.java deleted file mode 100644 index b02988c082..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/STObject.java +++ /dev/null @@ -1,489 +0,0 @@ -package com.ripple.core.coretypes; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ripple.core.coretypes.hash.HalfSha512; -import com.ripple.core.coretypes.hash.Hash128; -import com.ripple.core.coretypes.hash.Hash160; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.uint.UInt16; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.coretypes.uint.UInt8; -import com.ripple.core.fields.*; -import com.ripple.core.formats.Format; -import com.ripple.core.formats.LEFormat; -import com.ripple.core.formats.TxFormat; -import com.ripple.core.serialized.*; -import com.ripple.core.serialized.enums.EngineResult; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.serialized.enums.TransactionType; -import org.json.JSONObject; - -import java.util.EnumMap; -import java.util.Iterator; -import java.util.TreeMap; -import java.util.function.Predicate; - -public class STObject implements SerializedType, Iterable { - // Internally the fields are stored in a TreeMap - public static class FieldsMap extends TreeMap {} -// public static class FieldsMap extends HashMap {} - - protected FieldsMap fields; - public Format format; - - public STObject() { - fields = new FieldsMap(); - } - public STObject(FieldsMap fieldsMap) { - fields = fieldsMap; - } - - public static STObject fromJSON(String json) { - return fromJSONObject(new JSONObject(json)); - } - public static STObject fromJSONObject(JSONObject json) { - return translate.fromJSONObject(json); - } - public static STObject fromJacksonObject(ObjectNode object) { - return translate.fromJacksonObject(object); - } - public static STObject fromHex(String hex) { - return STObject.translate.fromHex(hex); - } - public static STObject fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - public static STObject fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - public static STObject fromParser(BinaryParser parser, Integer hint) { - return translate.fromParser(parser, hint); - } - - @Override - public Iterator iterator() { -// return fields.keySet().stream().sorted().iterator(); - return fields.keySet().iterator(); - } - - public String prettyJSON() { - return translate.toJSONObject(this).toString(2); - } - - /** - * @return a subclass of STObject using the same fields - * - * If the object has a TransactionType or LedgerEntryType - * then we can up)grade to a child class, with more specific - * helper methods, and we can use `instanceof` to great effect. - */ - public static STObject formatted(STObject source) { - return STObjectFormatter.format(source); - - } - - public Format getFormat() { - if (format == null) computeFormat(); - return format; - } - - public static class FormatException extends RuntimeException { - FormatException(String s) { - super(s); - } - } - - public void checkFormat() { - Format fmt = getFormat(); - EnumMap requirements = fmt.requirements(); - for (Field field : this) { - if (!requirements.containsKey(field)) { - throw new FormatException(fmt.name() + - " doesn't have field: " + field); - } - } - for (Field field : requirements.keySet()) { - Format.Requirement req = requirements.get(field); - if (!has(field)) { - if (req == Format.Requirement.REQUIRED) { - throw new FormatException(fmt.name() + - " requires " + field + " of type " - + field.getType()); - } - } else { - SerializedType type = get(field); - if (type.type() != field.getType()) { - if (!(field.getType() == Type.Hash160 && - type.type() == Type.AccountID)) { - throw new FormatException(type.toString() + - " is not " + field.getType()); - } - } - } - } - } - - public void setFormat(Format format) { - this.format = format; - } - - private void computeFormat() { - UInt16 tt = get(UInt16.TransactionType); - if (tt != null) { - setFormat(TxFormat.fromNumber(tt)); - } - UInt16 let = get(UInt16.LedgerEntryType); - if (let != null) { - setFormat(LEFormat.fromNumber(let)); - } - } - - public FieldsMap getFields() { - return fields; - } - - public SerializedType get(Field field) { - return fields.get(field); - } - - protected Hash256 signingHash(HashPrefix txSign) { - HalfSha512 signing = HalfSha512.prefixed256(txSign); - toBytesSink(signing, Field::isSigningField); - return signing.finish(); - } - - protected byte[] signingData(HashPrefix txSign) { - BytesList bl = new BytesList(); - bl.add(txSign.bytes()); - toBytesSink(bl, Field::isSigningField); - return bl.bytes(); - } - - protected static EngineResult engineResult(STObject obj) { - return (EngineResult) obj.get(Field.TransactionResult); - } - - static public LedgerEntryType ledgerEntryType(STObject obj) { - return (LedgerEntryType) obj.get(Field.LedgerEntryType); - } - - public static TransactionType transactionType(STObject obj) { - return (TransactionType) obj.get(Field.TransactionType); - } - - public SerializedType remove(Field f) { - return fields.remove(f); - } - - public boolean has(Field f) { - return fields.containsKey(f); - } - - public boolean has(T hf) { - return has(hf.getField()); - } - - public void put (UInt8Field f, UInt8 o) {put(f.getField(), o);} - public void put (Vector256Field f, Vector256 o) {put(f.getField(), o);} - public void put (BlobField f, Blob o) {put(f.getField(), o);} - public void put (UInt64Field f, UInt64 o) {put(f.getField(), o);} - public void put (UInt32Field f, UInt32 o) {put(f.getField(), o);} - public void put (UInt16Field f, UInt16 o) {put(f.getField(), o);} - public void put (PathSetField f, PathSet o) {put(f.getField(), o);} - public void put (STObjectField f, STObject o) {put(f.getField(), o);} - public void put (Hash256Field f, Hash256 o) {put(f.getField(), o);} - public void put (Hash160Field f, Hash160 o) {put(f.getField(), o);} - public void put (Hash128Field f, Hash128 o) {put(f.getField(), o);} - public void put (STArrayField f, STArray o) {put(f.getField(), o);} - public void put (AmountField f, Amount o) {put(f.getField(), o);} - public void put (AccountIDField f, AccountID o) {put(f.getField(), o);} - - public void putTranslated(T f, Object value) { - putTranslated(f.getField(), value); - } - - public STObject as(T f, Object value) { - putTranslated(f.getField(), value); - return this; - } - - public void put(Field f, SerializedType value) { - fields.put(f, value); - } - - public void putTranslated(Field f, Object value) { - TypeTranslator typeTranslator = Translators.forField(f); - SerializedType st; - try { - st = typeTranslator.fromValue(value); - } catch (Exception e) { - throw new RuntimeException("Couldn't put `" +value+ "` into field `" + f + "`", e); - } - fields.put(f, st); - } - - public AccountID get(AccountIDField f) { - return (AccountID) get(f.getField()); - } - - public Amount get(AmountField f) { - return (Amount) get(f.getField()); - } - - public STArray get(STArrayField f) { - return (STArray) get(f.getField()); - } - - public Hash128 get(Hash128Field f) { - return (Hash128) get(f.getField()); - } - - public Hash160 get(Hash160Field f) { - return (Hash160) get(f.getField()); - } - - public Hash256 get(Hash256Field f) { - return (Hash256) get(f.getField()); - } - - public STObject get(STObjectField f) { - return (STObject) get(f.getField()); - } - - public PathSet get(PathSetField f) { - return (PathSet) get(f.getField()); - } - - public UInt16 get(UInt16Field f) { - return (UInt16) get(f.getField()); - } - - public UInt32 get(UInt32Field f) { - return (UInt32) get(f.getField()); - } - - public UInt64 get(UInt64Field f) { - return (UInt64) get(f.getField()); - } - - public UInt8 get(UInt8Field f) { - return (UInt8) get(f.getField()); - } - - public Vector256 get(Vector256Field f) { - return (Vector256) get(f.getField()); - } - - public Blob get(BlobField f) { - return (Blob) get(f.getField()); - } - - // SerializedTypes implementation - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - public JSONObject toJSONObject() { - return translate.toJSONObject(this); - } - - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - public void toBytesSink(BytesSink to, Predicate p) { - BinarySerializer serializer = new BinarySerializer(to); - - for (Field field : this) { - if (p.test(field)) { - SerializedType value = fields.get(field); - serializer.add(field, value); - } - } - } - @Override - public void toBytesSink(BytesSink to) { - toBytesSink(to, field -> field.isSerialized()); - } - - @Override - public Type type() { - return Type.STObject; - } - - private static class Translator extends TypeTranslator { - - @Override - public STObject fromParser(BinaryParser parser, Integer hint) { - STObject so = new STObject(); - TypeTranslator tr; - SerializedType st; - Field field; - Integer sizeHint; - - // hint, is how many bytes to parse - if (hint != null) { - // end hint - hint = parser.pos() + hint; - } - - while (!parser.end(hint)) { - field = parser.readField(); - if (field == Field.ObjectEndMarker) { - break; - } - tr = Translators.forField(field); - sizeHint = field.isVLEncoded() ? parser.readVLLength() : null; - st = tr.fromParser(parser, sizeHint); - if (st == null) { - throw new IllegalStateException("Parsed " + field + " as null"); - } - so.put(field, st); - } - - return STObject.formatted(so); - } - - @Override - public Object toJSON(STObject obj) { - return toJSONObject(obj); - } - - public JSONObject toJSONObject(STObject obj) { - JSONObject json = new JSONObject(); - - for (Field f : obj) { - SerializedType obj1 = obj.get(f); - Object object = obj1.toJSON(); - json.put(f.name(), object); - } - - return json; - } - - @Override - public STObject fromJSONObject(JSONObject jsonObject) { - STObject so = new STObject(); - - Iterator keys = jsonObject.keys(); - while (keys.hasNext()) { - String key = (String) keys.next(); - Object value = jsonObject.get(key); - Field fieldKey = Field.fromString(key); - if (fieldKey == null) { - continue; - } - so.putTranslated(fieldKey, value); - } - return STObject.formatted(so); - } - - @Override - public STObject fromJacksonObject(ObjectNode object) { - STObject so = new STObject(); - - Iterator keys = object.fieldNames(); - while (keys.hasNext()) { - String key = keys.next(); - JsonNode value = object.get(key); - Field fieldKey = Field.fromString(key); - if (fieldKey == null) { - continue; - } - so.putTranslated(fieldKey, value); - } - return STObject.formatted(so); - } - } - - public int size() { - return fields.size(); - } - - static private Translator translate = new Translator(); - - private static STObjectField stobjectField(final Field f) { - return new STObjectField() {@Override public Field getField() {return f; } }; - } - - static public STObjectField TransactionMetaData = stobjectField(Field.TransactionMetaData); - static public STObjectField CreatedNode = stobjectField(Field.CreatedNode); - static public STObjectField DeletedNode = stobjectField(Field.DeletedNode); - static public STObjectField ModifiedNode = stobjectField(Field.ModifiedNode); - static public STObjectField PreviousFields = stobjectField(Field.PreviousFields); - static public STObjectField FinalFields = stobjectField(Field.FinalFields); - static public STObjectField NewFields = stobjectField(Field.NewFields); - static public STObjectField TemplateEntry = stobjectField(Field.TemplateEntry); - static public STObjectField Signer = stobjectField(Field.Signer); - static public STObjectField SignerEntry = stobjectField(Field.SignerEntry); - static public STObjectField ObjectEndMarker = stobjectField(Field.ObjectEndMarker); - static public STObjectField Memo = stobjectField(Field.Memo); - static public STObjectField Majority = stobjectField(Field.Majority); - - private static class Translators { - @SuppressWarnings("unused") - private static TypeTranslator get(Class kls) { - try { - java.lang.reflect.Field translate = kls.getDeclaredField("translate"); - translate.setAccessible(true); - return (TypeTranslator) translate.get(kls); - } catch (Exception e) { - throw new RuntimeException("for kls: " + kls.getSimpleName(), e); - } - } - - private static TypeTranslator forType(Type type) { - switch (type) { - case STObject: return STObject.translate; // get(STObject.class); - case Amount: return Amount.translate; // get(Amount.class); - case UInt16: return UInt16.translate; // get(UInt16.class); - case UInt32: return UInt32.translate; // get(UInt32.class); - case UInt64: return UInt64.translate; // get(UInt64.class); - case Hash128: return Hash128.translate; // get(Hash128.class); - case Hash256: return Hash256.translate; // get(Hash256.class); - case Blob: return Blob.translate; // get(Blob.class); - case AccountID: return AccountID.translate; // get(AccountID.class); - case STArray: return STArray.translate; // get(STArray.class); - case UInt8: return UInt8.translate; // get(UInt8.class); - case Hash160: return Hash160.translate; // get(Hash160.class); - case PathSet: return PathSet.translate; // get(PathSet.class); - case Vector256: return Vector256.translate; // get(Vector256.class); - default: throw new IllegalStateException("Unknown type"); - } - } - - private static TypeTranslator forField(Field field) { - if (field.tag == null) { - switch (field) { - case LedgerEntryType: - field.tag = LedgerEntryType.translate; //get(LedgerEntryType.class); - break; - case TransactionType: - field.tag = TransactionType.translate;// get(TransactionType.class); - break; - case TransactionResult: - field.tag = EngineResult.translate; // get(EngineResult.class); - break; - default: - field.tag = forType(field.getType()); - break; - } - } - return getCastedTag(field); - } - - @SuppressWarnings("unchecked") - private static TypeTranslator getCastedTag(Field field) { - return (TypeTranslator) field.tag; - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/STObjectFormatter.java b/ripple-core/src/main/java/com/ripple/core/coretypes/STObjectFormatter.java deleted file mode 100644 index d17b6ae0ee..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/STObjectFormatter.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.ripple.core.coretypes; - -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.generic.Validation; -import com.ripple.core.types.known.sle.LedgerHashes; -import com.ripple.core.types.known.sle.entries.*; -import com.ripple.core.types.known.tx.result.AffectedNode; -import com.ripple.core.types.known.tx.result.TransactionMeta; -import com.ripple.core.types.known.tx.txns.*; -import com.ripple.core.types.known.tx.txns.pseudo.EnableAmendment; -import com.ripple.core.types.known.tx.txns.pseudo.SetFee; - -public class STObjectFormatter { - public static STObject format(STObject source) { - // This would need to go before the test that just checks - // for ledgerEntryType - if (AffectedNode.isAffectedNode(source)) { - return new AffectedNode(source); - } - - if (TransactionMeta.isTransactionMeta(source)) { - TransactionMeta meta = new TransactionMeta(); - meta.fields = source.fields; - return meta; - } - - LedgerEntryType ledgerEntryType = STObject.ledgerEntryType(source); - if (ledgerEntryType != null) { - return ledgerFormatted(source, ledgerEntryType); - } - - TransactionType transactionType = STObject.transactionType(source); - if (transactionType != null) { - return transactionFormatted(source, transactionType); - } - - if (Validation.isValidation(source)) { - Validation validation = new Validation(); - validation.fields = source.fields; - return validation; - } - - return source; - } - - private static STObject transactionFormatted(STObject source, TransactionType transactionType) { - STObject constructed = null; - switch (transactionType) { - case Payment: - constructed = new Payment(); - break; - case EscrowCreate: - constructed = new EscrowCreate(); - break; - case EscrowFinish: - constructed = new EscrowFinish(); - break; - case AccountSet: - constructed = new AccountSet(); - break; - case EscrowCancel: - constructed = new EscrowCancel(); - break; - case SetRegularKey: - constructed = new SetRegularKey(); - break; - case OfferCreate: - constructed = new OfferCreate(); - break; - case OfferCancel: - constructed = new OfferCancel(); - break; - case TicketCreate: - constructed = new TicketCreate(); - break; - case TicketCancel: - constructed = new TicketCancel(); - break; - case SignerListSet: - constructed = new SignerListSet(); - break; - case PaymentChannelCreate: - constructed = new PaymentChannelCreate(); - break; - case PaymentChannelFund: - constructed = new PaymentChannelFund(); - break; - case PaymentChannelClaim: - constructed = new PaymentChannelClaim(); - break; - case CheckCreate: - constructed = new CheckCreate(); - break; - case CheckCash: - constructed = new CheckCash(); - break; - case CheckCancel: - constructed = new CheckCancel(); - break; - case DepositPreauth: - constructed = new DepositPreauth(); - break; - case TrustSet: - constructed = new TrustSet(); - break; - case EnableAmendment: - constructed = new EnableAmendment(); - break; - case SetFee: - constructed = new SetFee(); - break; - } - - constructed.fields = source.fields; - return constructed; - - } - - private static STObject ledgerFormatted(STObject source, LedgerEntryType ledgerEntryType) { - STObject constructed = null; - switch (ledgerEntryType) { - case Escrow: - constructed = new Escrow(); - break; - case Offer: - constructed = new Offer(); - break; - case RippleState: - constructed = new RippleState(); - break; - case AccountRoot: - constructed = new AccountRoot(); - break; - case DirectoryNode: - if (source.has(AccountID.Owner)) { - constructed = new OwnerDirectory(); - } else { - constructed = new OfferDirectory(); - } - break; - case LedgerHashes: - constructed = new LedgerHashes(); - break; - case Amendments: - constructed = new Amendments(); - break; - case FeeSettings: - constructed = new FeeSettings(); - break; - case Ticket: - constructed = new Ticket(); - break; - case SignerList: - constructed = new SignerList(); - break; - case PayChannel: - constructed = new PayChannel(); - break; - case Check: - constructed = new Check(); - break; - case DepositPreauth: - constructed = new DepositPreauthLe(); - break; - } - constructed.fields = source.fields; - return constructed; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/Vector256.java b/ripple-core/src/main/java/com/ripple/core/coretypes/Vector256.java deleted file mode 100644 index 8a5eef49e5..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/Vector256.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.ripple.core.coretypes; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; -import com.ripple.core.fields.Vector256Field; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import com.ripple.encodings.common.B16; -import org.json.JSONArray; - -import java.util.ArrayList; - -public class Vector256 extends ArrayList implements SerializedType { - - @Override - public Object toJSON() { - return toJSONArray(); - } - - public JSONArray toJSONArray() { - JSONArray array = new JSONArray(); - - for (Hash256 hash256 : this) { - array.put(hash256.toString()); - } - - return array; - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - for (Hash256 hash256 : this) { - hash256.toBytesSink(to); - } - } - - @Override - public Type type() { - return Type.Vector256; - } - - /** - * This method puts the last element in the removed elements slot, and - * pops off the back, thus preserving contiguity but losing ordering. - * @param ledgerIndex the ledger entry index to remove - * - * Unused when the featureSortedDirectories amendment is applied: - * See: https://ripple.com/build/known-amendments/#sorteddirectories - */ - public boolean removeUnstable(Hash256 ledgerIndex) { - int i = indexOf(ledgerIndex); - if (i == -1) { - return false; - } - - int last = size() - 1; - Hash256 lastIndex = get(last); - set(i, lastIndex); - remove(last); - - return true; - } - - public static Vector256 fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - - public static Vector256 fromHex(String hex) { - return translate.fromHex(hex); - } - public static Vector256 fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - public static class Translator extends TypeTranslator { - @Override - public Vector256 fromParser(BinaryParser parser, Integer hint) { - Vector256 vector256 = new Vector256(); - if (hint == null) { - hint = parser.size() - parser.pos(); - } - for (int i = 0; i < hint / 32; i++) { - vector256.add(Hash256.fromParser(parser)); - } - - return vector256; - } - - public JSONArray toJSONArray(Vector256 obj) { - return obj.toJSONArray(); - } - - @Override - public Vector256 fromJSONArray(JSONArray jsonArray) { - Vector256 vector = new Vector256(); - - for (int i = 0; i < jsonArray.length(); i++) { - String hex = jsonArray.getString(i); - vector.add(new Hash256(B16.decode(hex))); - } - - return vector; - } - @Override - public Vector256 fromJacksonArray(ArrayNode jsonArray) { - Vector256 vector = new Vector256(); - - for (int i = 0; i < jsonArray.size(); i++) { - String hex = jsonArray.get(i).asText(); - vector.add(new Hash256(B16.decode(hex))); - } - - return vector; - } - } - static public Translator translate = new Translator(); - - public Vector256(){} - - public static Vector256Field vector256Field(final Field f) { - return new Vector256Field(){ @Override public Field getField() {return f;}}; - } - - static public Vector256Field Indexes = vector256Field(Field.Indexes); - static public Vector256Field Hashes = vector256Field(Field.Hashes); - static public Vector256Field Amendments = vector256Field(Field.Amendments); -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/HalfSha512.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/HalfSha512.java deleted file mode 100644 index 560cce14a0..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/HalfSha512.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.ripple.core.coretypes.hash; - -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; - -import java.security.MessageDigest; - -public class HalfSha512 implements BytesSink { - private MessageDigest messageDigest; - - public HalfSha512() { - try { - messageDigest = MessageDigest.getInstance("SHA-512"); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static HalfSha512 prefixed256(Prefix bytes) { - HalfSha512 halfSha512 = new HalfSha512(); - halfSha512.update(bytes); - return halfSha512; - } - - public void update(byte[] bytes) { - messageDigest.update(bytes); - } - - public void update(Hash256 hash) { - messageDigest.update(hash.bytes()); - } - - public MessageDigest digest() { - return messageDigest; - } - - public Hash256 finish() { - byte[] half = digestBytes(); - return new Hash256(half); - } - - private byte[] digestBytes() { - byte[] digest = messageDigest.digest(); - byte[] half = new byte[32]; - System.arraycopy(digest, 0, half, 0, 32); - return half; - } - - @Override - public void add(byte aByte) { - messageDigest.update(aByte); - } - - @Override - public void add(byte[] bytes) { - messageDigest.update(bytes); - } - - public void update(Prefix prefix) { - messageDigest.update(prefix.bytes()); - } - - public HalfSha512 add(SerializedType st) { - st.toBytesSink(this); - return this; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash.java deleted file mode 100644 index 898d1b2913..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.ripple.core.coretypes.hash; - -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import com.ripple.encodings.common.B16; - -import java.math.BigInteger; -import java.util.Arrays; - -abstract public class Hash implements SerializedType, Comparable { - protected final byte[] hash; - - public Hash(byte[] bytes, int size) { - hash = checkHash(bytes, size); - } - - @Override - public String toString() { - return B16.encode(hash); - } - - @Override - public int hashCode() { - // Having this lazily computed and then cached would give significant - // performance boosts in some single threaded contexts. Given the inputs - // for the method are at least effectively final, and the method is - // essentially a pure function, it seems probably safe to do. It - // seems at worst, that in a multi threaded context, some extra work - // will be done in rare cases? - // TODO: check this assumption - return Arrays.hashCode(hash); - } - - private byte[] checkHash(byte[] bytes, int size) { - int length = bytes.length; - if (length > size) { - throwIllegalArg(length, "wide"); - } else if (length == size) { - // TODO: What costs for this ? - return bytes.clone(); - } else { - throwIllegalArg(length, "small"); - } - throw new IllegalStateException("Can not get here"); - } - - private void throwIllegalArg(int length, String wrongness) { - String simpleName = getClass().getSimpleName(); - throw new IllegalArgumentException("Hash length of " + length + "bytes is too " + - wrongness + - " for " + simpleName); - } - - BigInteger bigInteger() { - return new BigInteger(1, hash); - } - - // TODO: Is a defensive copy worthwhile? Or just be adults? Never been a - // problem in practice, though others may not know to avoid mutating the - // returned array. Pity Java doesn't have something like C++ const. - // If you did it in one place, should do it EVERY WHERE, inputs/outputs and - // on every type that stores mutable arrays internally and the perf and - // maybe memory (at least GC stress) costs could add up. - // It's a bit like polishing a tur*. - public byte[] bytes() { - return hash.clone(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Hash) { - return Arrays.equals(hash, ((Hash) obj).hash); - } - - return super.equals(obj); - } - - @Override - public int compareTo(Subclass another) { - byte[] thisBytes = bytes(); - byte[] bytes = another.bytes(); - - return compareBytes(thisBytes, bytes, 0, thisBytes.length); - } - - public int compareStartingAt(Subclass another, int start) { - byte[] thisBytes = bytes(); - byte[] bytes = another.bytes(); - - return compareBytes(thisBytes, bytes, start, thisBytes.length); - } - - private int compareBytes(byte[] thisBytes, byte[] bytes, int start, int numBytes) { - int thisLength = thisBytes.length; - if (!(bytes.length == thisLength)) { - throw new RuntimeException(); - } - - for (int i = start; i < numBytes; i++) { - int cmp = (thisBytes[i] & 0xFF) - (bytes[i] & 0xFF); - if (cmp != 0) { - return cmp < 0 ? -1 : 1; - } - } - return 0; - } - - public byte[] slice(int start) { - return slice(start, 0); - } - - public byte get(int i) { - if (i < 0) i += hash.length; - return hash[i]; - } - - private byte[] slice(int start, int end) { - if (start < 0) start += hash.length; - if (end <= 0) end += hash.length; - - int length = end - start; - byte[] slice = new byte[length]; - - System.arraycopy(hash, start, slice, 0, length); - return slice; - } - - static public abstract class HashTranslator extends TypeTranslator { - - public abstract T newInstance(byte[] b); - public abstract int byteWidth(); - - @Override - public T fromParser(BinaryParser parser, Integer hint) { - return newInstance(parser.read(byteWidth())); - } - - @Override - public Object toJSON(T obj) { - return B16.encode(obj.hash); - } - - @Override - public T fromString(String value) { - return newInstance(B16.decode(value)); - } - - @Override - public void toBytesSink(T obj, BytesSink to) { - to.add(obj.bytes()); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash128.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash128.java deleted file mode 100644 index 112340da9f..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash128.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.ripple.core.coretypes.hash; - -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Hash128Field; -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; - -public class Hash128 extends Hash { - public Hash128(byte[] bytes) { - super(bytes, 16); - } - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - @Override - public Type type() { - return Type.Hash128; - } - - public static Hash128 fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - - public static Hash128 fromHex(String string) { - return translate.fromHex(string); - } - public static Hash128 fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - public static class Translator extends HashTranslator { - @Override - public Hash128 newInstance(byte[] b) { - return new Hash128(b); - } - - @Override - public int byteWidth() { - return 16; - } - } - - public static Translator translate = new Translator(); - - private static Hash128Field hash128Field(final Field f) { - return new Hash128Field(){ @Override public Field getField() {return f;}}; - } - - static public Hash128Field EmailHash = hash128Field(Field.EmailHash); - -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash160.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash160.java deleted file mode 100644 index 5f237c195c..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash160.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.ripple.core.coretypes.hash; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Hash160Field; -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; - -public class Hash160 extends Hash { - public Hash160(byte[] bytes) { - super(bytes, 20); - } - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - @Override - public Type type() { - return Type.Hash160; - } - - public static Hash160 fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - - public static Hash160 fromHex(String string) { - return translate.fromHex(string); - } - - public static Hash160 fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - public static class Translator extends HashTranslator { - @Override - public Hash160 newInstance(byte[] b) { - return new Hash160(b); - } - - @Override - public int byteWidth() { - return 20; - } - - @Override - public Hash160 fromString(String value) { - if (value.startsWith("r")) { - return newInstance(AccountID.fromAddress(value).bytes()); - } - return super.fromString(value); - } - } - public static Translator translate = new Translator(); - - private static Hash160Field hash160Field(final Field f) { - return new Hash160Field(){ @Override public Field getField() {return f;}}; - } - - static public Hash160Field TakerPaysIssuer = hash160Field(Field.TakerPaysIssuer); - static public Hash160Field TakerGetsCurrency = hash160Field(Field.TakerGetsCurrency); - static public Hash160Field TakerPaysCurrency = hash160Field(Field.TakerPaysCurrency); - static public Hash160Field TakerGetsIssuer = hash160Field(Field.TakerGetsIssuer); -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash256.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash256.java deleted file mode 100644 index 678b15ef00..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Hash256.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.ripple.core.coretypes.hash; - -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Hash256Field; -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; - -import java.math.BigInteger; -import java.util.TreeMap; - -public class Hash256 extends Hash { - - public static final BigInteger bookBaseSize = new BigInteger("10000000000000000", 16); - - public int divergenceDepth(Hash256 other) { - return divergenceDepth(0, other); - } - public int divergenceDepth(int i, Hash256 other) { - for (; i < 64; i++) { - if (nibblet(i) != other.nibblet(i)) { - break; - } - } - return i; - } - - public static class Hash256Map extends TreeMap { - public Hash256Map(Hash256Map cache) { - super(cache); - } - public Hash256Map() { - - } - } - public static final Hash256 ZERO_256 = new Hash256(new byte[32]); - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - @Override - public Type type() { - return Type.Hash256; - } - - public boolean isZero() { - return this == Hash256.ZERO_256 || equals(Hash256.ZERO_256); - } - - public boolean isNonZero() { - return !isZero(); - } - - public static Hash256 fromHex(String s) { - return translate.fromHex(s); - } - public static Hash256 fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - public static Hash256 fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - public Hash256(byte[] bytes) { - super(bytes, 32); - } - - public static Hash256 signingHash(byte[] blob) { - return prefixedHalfSha512(HashPrefix.txSign, blob); - } - - public static Hash256 prefixedHalfSha512(Prefix prefix, byte[] blob) { - HalfSha512 messageDigest = HalfSha512.prefixed256(prefix); - messageDigest.update(blob); - return messageDigest.finish(); - } - - public int nibblet(int depth) { - int byte_ix = depth > 0 ? depth / 2 : 0; - int b = super.hash[byte_ix]; - if (depth % 2 == 0) { - b = (b & 0xF0) >> 4; - } else { - b = b & 0x0F; - } - return b; - } - - public static class Translator extends HashTranslator { - @Override - public Hash256 newInstance(byte[] b) { - return new Hash256(b); - } - - @Override - public int byteWidth() { - return 32; - } - } - public static Translator translate = new Translator(); - - public static Hash256Field hash256Field(final Field f) { - return new Hash256Field(){ @Override public Field getField() {return f;}}; - } - - static public Hash256Field LedgerHash = hash256Field(Field.LedgerHash); - static public Hash256Field ParentHash = hash256Field(Field.ParentHash); - static public Hash256Field TransactionHash = hash256Field(Field.TransactionHash); - static public Hash256Field AccountHash = hash256Field(Field.AccountHash); - static public Hash256Field PreviousTxnID = hash256Field(Field.PreviousTxnID); - static public Hash256Field AccountTxnID = hash256Field(Field.AccountTxnID); - static public Hash256Field LedgerIndex = hash256Field(Field.LedgerIndex); - static public Hash256Field WalletLocator = hash256Field(Field.WalletLocator); - static public Hash256Field RootIndex = hash256Field(Field.RootIndex); - static public Hash256Field BookDirectory = hash256Field(Field.BookDirectory); - static public Hash256Field InvoiceID = hash256Field(Field.InvoiceID); - static public Hash256Field Nickname = hash256Field(Field.Nickname); - static public Hash256Field Amendment = hash256Field(Field.Amendment); - static public Hash256Field TicketID = hash256Field(Field.TicketID); - static public Hash256Field Channel = hash256Field(Field.Channel); - static public Hash256Field CheckID = hash256Field(Field.CheckID); - static public Hash256Field Digest = hash256Field(Field.Digest); - static public Hash256Field ConsensusHash = hash256Field(Field.ConsensusHash); - - static public Hash256Field hash = hash256Field(Field.hash); - static public Hash256Field index = hash256Field(Field.index); -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Index.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Index.java deleted file mode 100644 index 75a5900430..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/Index.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.ripple.core.coretypes.hash; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Currency; -import com.ripple.core.coretypes.Issue; -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.hash.prefixes.LedgerSpace; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; - -import java.util.Arrays; -import java.util.List; - -import static com.ripple.core.coretypes.hash.HalfSha512.prefixed256; -import static java.util.Collections.sort; - -public class Index { - private static Hash256 createBookBase(Issue pays, Issue gets) { - return prefixed256(LedgerSpace.bookDir) - .add(pays.currency()) - .add(gets.currency()) - .add(pays.issuer()) - .add(gets.issuer()) - .finish(); - } - - /** - * - * @return a copy of index, with quality overlaid in lowest 8 bytes - */ - public static Hash256 quality(Hash256 index, UInt64 quality) { - byte[] qi = new byte[32]; - System.arraycopy(index.bytes(), 0, qi, 0, 24); - if (quality != null) System.arraycopy(quality.toBytes(), 0, qi, 24, 8); - return new Hash256(qi); - } - - /** - * @return A copy of index, with the lowest 8 bytes all zeroed. - */ - private static Hash256 zeroQuality(Hash256 fullIndex) { - return quality(fullIndex, null); - } - - public static Hash256 rippleState(AccountID a1, AccountID a2, Currency currency) { - List accounts = Arrays.asList(a1, a2); - sort(accounts); - return rippleState(accounts, currency); - } - - public static Hash256 rippleState(List sortedAccounts, Currency currency) { - HalfSha512 hasher = prefixed256(LedgerSpace.ripple); - // Low then High - for (AccountID account : sortedAccounts) account.toBytesSink(hasher); - // Currency - currency.toBytesSink(hasher); - - return hasher.finish(); - } - - /** - * - * @param rootIndex The RootIndex index for the directory node - * @param nodeIndex nullable LowNode, HighNode, OwnerNode, BookNode etc - * defining a `page` number. - * - * @return A hash of rootIndex and nodeIndex when nodeIndex is non default - * else the rootIndex. This hash is used as an index for the next - * DirectoryNode page. - */ - public static Hash256 directoryNode(Hash256 rootIndex, UInt64 nodeIndex) { - if (nodeIndex == null || nodeIndex.isZero()) { - return rootIndex; - } - - return prefixed256(LedgerSpace.dirNode) - .add(rootIndex) - .add(nodeIndex) - .finish(); - } - - public static Hash256 accountRoot(AccountID accountID) { - return prefixed256(LedgerSpace.account).add(accountID).finish(); - } - - public static Hash256 paymentChannel(AccountID account, AccountID destination, UInt32 sequence) { - return prefixed256(LedgerSpace.paymentChannel) - .add(account) - .add(destination) - .add(sequence) - .finish(); - } - - public static Hash256 escrow(AccountID account, UInt32 sequence) { - return prefixed256(LedgerSpace.escrow) - .add(account) - .add(sequence) - .finish(); - } - - public static Hash256 ownerDirectory(AccountID account) { - return Hash256.prefixedHalfSha512(LedgerSpace.ownerDir, account.bytes()); - } - - public static Hash256 transactionID(byte[] blob) { - return Hash256.prefixedHalfSha512(HashPrefix.transactionID, blob); - } - - public static Hash256 bookStart(Issue pays, Issue gets) { - return zeroQuality(createBookBase(pays, gets)); - } - - public static Hash256 bookStart(Hash256 indexFromBookRange) { - return zeroQuality(indexFromBookRange); - } - - public static Hash256 bookEnd(Hash256 base) { - byte[] end = base.bigInteger().add(Hash256.bookBaseSize).toByteArray(); - if (end.length > 32) { - byte[] source = end; - end = new byte[32]; - System.arraycopy(source, source.length - 32, end, 0, 32); - } - return new Hash256(end); - } - - public static Hash256 ledgerHashes(long prev) { - return prefixed256(LedgerSpace.skipList) - .add(new UInt32(prev >> 16)) - .finish(); - } - public static Hash256 ledgerHashes() { - return prefixed256(LedgerSpace.skipList).finish(); - } - - public static Hash256 amendments() { - return prefixed256(LedgerSpace.amendment).finish(); - } - - public static Hash256 feeSettings() { - return prefixed256(LedgerSpace.fee).finish(); - } - - public static Hash256 signerList(AccountID account) { - return prefixed256(LedgerSpace.signerList) - .add(account) - .add(UInt32.ZERO) - .finish(); - } - - public static Hash256 ticket(AccountID account, UInt32 sequence) { - return prefixed256(LedgerSpace.ticket) - .add(account) - .add(sequence) - .finish(); - } - - -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/HashPrefix.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/HashPrefix.java deleted file mode 100644 index f213d4eb38..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/HashPrefix.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.ripple.core.coretypes.hash.prefixes; - -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.encodings.common.B16; - -/** - * The prefix codes are part of the Ripple protocol - * and existing codes cannot be arbitrarily changed. - */ -public enum HashPrefix implements Prefix { - transactionID ('T', 'X', 'N'), - txNode ('S', 'N', 'D'), - leafNode ('M', 'L', 'N'), - innerNode ('M', 'I', 'N'), - innerNodeV2 ('I', 'N', 'R'), - ledgerMaster ('L', 'W', 'R'), - txSign ('S', 'T', 'X'), - txMultiSign ('S', 'M', 'T'), - validation ('V', 'A', 'L'), - proposal ('P', 'R', 'P'), - manifest ('M', 'A', 'N'), - paymentChannelClaim ('C', 'L', 'M'); - - private UInt32 uInt32; - private byte[] bytes; - private String chars; - - @Override - public byte[] bytes() { - return bytes; - } - - public String toHex() { - return B16.encode(bytes); - } - - HashPrefix(char... chars) { - this.chars = new String(chars); - byte[] bytes = { - (byte) chars[0], - (byte) chars[1], - (byte) chars[2], - (byte) 0, - }; - uInt32 = UInt32.fromBytes(bytes); - this.bytes = bytes; - } - - public UInt32 uInt32() { - return uInt32; - } - - public String chars() { - return chars; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/LedgerSpace.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/LedgerSpace.java deleted file mode 100644 index 20f5d61388..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/LedgerSpace.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.ripple.core.coretypes.hash.prefixes; - -import com.ripple.core.coretypes.uint.UInt16; - -public enum LedgerSpace implements Prefix { - account('a'), - dirNode('d'), - generator('g'), - - ripple('r'), - offer('o'), // Entry for an offer. - ownerDir('O'), // Directory of things owned by an account. - bookDir('B'), // Directory of order books. - contract('c'), - skipList('s'), - escrow('u'), - amendment('f'), - fee('e'), - ticket('T'), - signerList('S'), - paymentChannel('x'), - - // no longer used - nickname('n'),; - - UInt16 uInt16; - public byte[] bytes; - - @Override - public byte[] bytes() { - return bytes; - } - - LedgerSpace(char c) { - uInt16 = new UInt16((int) c); - bytes = uInt16.toByteArray(); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/Prefix.java b/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/Prefix.java deleted file mode 100644 index 7ba8b7a62a..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/hash/prefixes/Prefix.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.ripple.core.coretypes.hash.prefixes; - -public interface Prefix { - byte[] bytes(); -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt.java b/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt.java deleted file mode 100644 index aae0dcedf3..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.ripple.core.coretypes.uint; - -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import com.ripple.encodings.common.B16; -import com.ripple.utils.Utils; - -import java.math.BigInteger; - -abstract public class UInt extends Number implements SerializedType, Comparable { - private final BigInteger value; - - private static BigInteger[] upperBounds = new BigInteger[8]; - private static BigInteger maxUIntVal(int bits) { - return new BigInteger("2").pow(bits).subtract(BigInteger.ONE); - } - - static { - for (int i = 0; i < 8; i++) { - upperBounds[i] = maxUIntVal((i + 1) * 8 ); - } - } - - UInt(byte[] bytes) { - value = new BigInteger(1, bytes); - checkBounds(); - } - - private void checkBounds() { - BigInteger upper = upperBounds[getByteWidth() - 1]; - if (value.compareTo(upper) > 0 || value.compareTo(BigInteger.ZERO) < 0) { - throw new IllegalArgumentException("value `" + value + - "` is illegal for " + getClass().getSimpleName()); - } - } - - UInt(BigInteger bi) { - value = (bi); - checkBounds(); - } - UInt(Number s) { - value = (BigInteger.valueOf(s.longValue())); - checkBounds(); - } - UInt(String s) { - value = (new BigInteger(s)); - checkBounds(); - } - UInt(String s, int radix) { - value = (new BigInteger(s, radix)); - checkBounds(); - } - - @Override - public String toString() { - return value.toString(); - } - - public abstract int getByteWidth(); - protected abstract Subclass instanceFrom(BigInteger n); - - public Subclass add(UInt val) { - return instanceFrom(value.add(val.value)); - } - - public Subclass subtract(UInt val) { - return instanceFrom(value.subtract(val.value)); - } - - public Subclass multiply(UInt val) { - return instanceFrom(value.multiply(val.value)); - } - - public Subclass divide(UInt val) { - return instanceFrom(value.divide(val.value)); - } - - public Subclass or(UInt val) { - return instanceFrom(value.or(val.value)); - } - - public Subclass shiftLeft(int n) { - return instanceFrom(value.shiftLeft(n)); - } - - public Subclass shiftRight(int n) { - return instanceFrom(value.shiftRight(n)); - } - - public int compareTo(UInt val) { - return value.compareTo(val.value); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof UInt) { - return equals((UInt) obj); - } - else return super.equals(obj); - } - - public boolean equals(UInt x) { - return value.equals(x.value); - } - - public String toString(int radix) { - return value.toString(radix); - } - public byte[] toByteArray() { - int length = getByteWidth(); - return Utils.leadingZeroesTrimmedOrPaddedTo(length, value.toByteArray()); - } - - abstract public Object value(); - - public BigInteger bigInteger(){ - return value; - } - - @Override - public int intValue() { - return value.intValue(); - } - - @Override - public long longValue() { - return value.longValue(); - } - - @Override - public double doubleValue() { - return value.doubleValue(); - } - - @Override - public float floatValue() { - return value.floatValue(); - } - - @Override - public byte byteValue() { - return value.byteValue(); - } - - @Override - public short shortValue() { - return value.shortValue(); - } - - public boolean lte(T sequence) { - return compareTo(sequence) < 1; - } - - public boolean testBit(int f) { - // TODO, optimized ;) // move to Uint32 - return value.testBit(f); - } - - public boolean isZero() { - return value.signum() == 0; - } - - static public abstract class UINTTranslator extends TypeTranslator { - public abstract T newInstance(BigInteger i); - public abstract int byteWidth(); - - @Override - public T fromParser(BinaryParser parser, Integer hint) { - return newInstance(new BigInteger(1, parser.read(byteWidth()))); - } - - @Override - public Object toJSON(T obj) { - if (obj.getByteWidth() <= 4) { - return obj.longValue(); - } else { - return toString(obj); - } - } - - @Override - public T fromLong(long aLong) { - return newInstance(BigInteger.valueOf(aLong)); - } - - @Override - public T fromString(String value) { - int radix = byteWidth() <= 4 ? 10 : 16; - return newInstance(new BigInteger(value, radix)); - } - - @Override - public T fromInteger(int integer) { - return fromLong(integer); - } - - @Override - public String toString(T obj) { - return B16.encode(obj.toByteArray()); - } - - @Override - public void toBytesSink(T obj, BytesSink to) { - to.add(obj.toByteArray()); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt16.java b/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt16.java deleted file mode 100644 index 9634cf6082..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt16.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.ripple.core.coretypes.uint; - -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; -import com.ripple.core.fields.UInt16Field; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.TypeTranslator; - -import java.math.BigInteger; - -public class UInt16 extends UInt { - public final static UInt16 ZERO = new UInt16(0); - - public static TypeTranslator translate = new UINTTranslator() { - @Override - public UInt16 newInstance(BigInteger i) { - return new UInt16(i); - } - - @Override - public int byteWidth() { - return 2; - } - }; - - public UInt16(byte[] bytes) { - super(bytes); - } - - public UInt16(BigInteger value) { - super(value); - } - - public UInt16(Number s) { - super(s); - } - - public UInt16(String s) { - super(s); - } - - public UInt16(String s, int radix) { - super(s, radix); - } - - public static UInt16 fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - - public static UInt16 fromHex(String string) { - return translate.fromHex(string); - } - - public static UInt16 fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - @Override - public int getByteWidth() { - return 2; - } - - @Override - protected UInt16 instanceFrom(BigInteger n) { - return new UInt16(n); - } - - @Override - public Integer value() { - return intValue(); - } - - private static UInt16Field int16Field(final Field f) { - return new UInt16Field(){ @Override public Field getField() {return f;}}; - } - - static public UInt16Field LedgerEntryType = int16Field(Field.LedgerEntryType); - static public UInt16Field TransactionType = int16Field(Field.TransactionType); - static public UInt16Field SignerWeight = int16Field(Field.SignerWeight); - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - @Override - public Type type() { - return Type.UInt16; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt32.java b/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt32.java deleted file mode 100644 index b831c8607c..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt32.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.ripple.core.coretypes.uint; - -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; -import com.ripple.core.fields.UInt32Field; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.TypeTranslator; - -import java.math.BigInteger; - -public class UInt32 extends UInt { - public final static UInt32 ZERO = new UInt32(0); - - public static TypeTranslator translate = new UINTTranslator() { - @Override - public UInt32 newInstance(BigInteger i) { - return new UInt32(i); - } - - @Override - public int byteWidth() { - return 4; - } - }; - - public UInt32(byte[] bytes) { - super(bytes); - } - - public UInt32(BigInteger value) { - super(value); - } - - public UInt32(Number val) { - super(val); - } - public UInt32(String s) { - super(s); - } - - public UInt32(String s, int radix) { - super(s, radix); - } - - public static UInt32 fromParser(BinaryParser binaryParser) { - return translate.fromParser(binaryParser); - } - public static UInt32 fromHex(String string) { - return translate.fromHex(string); - } - public static UInt32 fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - @Override - public int getByteWidth() { - return 4; - } - - @Override - protected UInt32 instanceFrom(BigInteger n) { - return new UInt32(n); - } - - @Override - public Long value() { - return longValue(); - } - - private static UInt32Field int32Field(final Field f) { - return new UInt32Field(){ @Override public Field getField() {return f;}}; - } - - static public UInt32Field Flags = int32Field(Field.Flags); - static public UInt32Field SourceTag = int32Field(Field.SourceTag); - static public UInt32Field Sequence = int32Field(Field.Sequence); - static public UInt32Field PreviousTxnLgrSeq = int32Field(Field.PreviousTxnLgrSeq); - static public UInt32Field LedgerSequence = int32Field(Field.LedgerSequence); - static public UInt32Field CloseTime = int32Field(Field.CloseTime); - static public UInt32Field ParentCloseTime = int32Field(Field.ParentCloseTime); - static public UInt32Field SigningTime = int32Field(Field.SigningTime); - static public UInt32Field Expiration = int32Field(Field.Expiration); - static public UInt32Field TransferRate = int32Field(Field.TransferRate); - static public UInt32Field WalletSize = int32Field(Field.WalletSize); - static public UInt32Field OwnerCount = int32Field(Field.OwnerCount); - static public UInt32Field DestinationTag = int32Field(Field.DestinationTag); - static public UInt32Field HighQualityIn = int32Field(Field.HighQualityIn); - static public UInt32Field HighQualityOut = int32Field(Field.HighQualityOut); - static public UInt32Field LowQualityIn = int32Field(Field.LowQualityIn); - static public UInt32Field LowQualityOut = int32Field(Field.LowQualityOut); - static public UInt32Field QualityIn = int32Field(Field.QualityIn); - static public UInt32Field QualityOut = int32Field(Field.QualityOut); - static public UInt32Field StampEscrow = int32Field(Field.StampEscrow); - static public UInt32Field BondAmount = int32Field(Field.BondAmount); - static public UInt32Field LoadFee = int32Field(Field.LoadFee); - static public UInt32Field OfferSequence = int32Field(Field.OfferSequence); - static public UInt32Field FirstLedgerSequence = int32Field(Field.FirstLedgerSequence); - static public UInt32Field LastLedgerSequence = int32Field(Field.LastLedgerSequence); - static public UInt32Field TransactionIndex = int32Field(Field.TransactionIndex); - static public UInt32Field OperationLimit = int32Field(Field.OperationLimit); - static public UInt32Field ReferenceFeeUnits = int32Field(Field.ReferenceFeeUnits); - static public UInt32Field ReserveBase = int32Field(Field.ReserveBase); - static public UInt32Field ReserveIncrement = int32Field(Field.ReserveIncrement); - static public UInt32Field SetFlag = int32Field(Field.SetFlag); - static public UInt32Field ClearFlag = int32Field(Field.ClearFlag); - static public UInt32Field SignerQuorum = int32Field(Field.SignerQuorum); - public static UInt32Field SignerListID = int32Field(Field.SignerListID); - public static UInt32Field CancelAfter = int32Field(Field.CancelAfter); - public static UInt32Field FinishAfter = int32Field(Field.FinishAfter); - public static UInt32Field SettleDelay = int32Field(Field.SettleDelay); - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - @Override - public Type type() { - return Type.UInt32; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt64.java b/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt64.java deleted file mode 100644 index 3312192542..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt64.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.ripple.core.coretypes.uint; - -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; -import com.ripple.core.fields.UInt64Field; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.TypeTranslator; - -import java.math.BigInteger; - -public class UInt64 extends UInt { - public final static UInt64 ZERO = new UInt64(0); - - public static TypeTranslator translate = new UINTTranslator() { - @Override - public UInt64 newInstance(BigInteger i) { - return new UInt64(i); - } - - @Override - public int byteWidth() { - return 8; - } - }; - - public UInt64(byte[] bytes) { - super(bytes); - } - - public UInt64(BigInteger value) { - super(value); - } - - public UInt64(Number s) { - super(s); - } - - public UInt64(String s) { - super(s); - } - - public UInt64(String s, int radix) { - super(s, radix); - } - - public static UInt64 fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - - public static UInt64 fromHex(String string) { - return translate.fromHex(string); - } - public static UInt64 fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - @Override - public int getByteWidth() { - return 8; - } - - @Override - protected UInt64 instanceFrom(BigInteger n) { - return new UInt64(n); - } - - @Override - public BigInteger value() { - return bigInteger(); - } - - private static UInt64Field int64Field(final Field f) { - return new UInt64Field(){ @Override public Field getField() {return f;}}; - } - - static public UInt64Field IndexNext = int64Field(Field.IndexNext); - static public UInt64Field IndexPrevious = int64Field(Field.IndexPrevious); - static public UInt64Field BookNode = int64Field(Field.BookNode); - static public UInt64Field OwnerNode = int64Field(Field.OwnerNode); - static public UInt64Field BaseFee = int64Field(Field.BaseFee); - static public UInt64Field ExchangeRate = int64Field(Field.ExchangeRate); - static public UInt64Field LowNode = int64Field(Field.LowNode); - static public UInt64Field HighNode = int64Field(Field.HighNode); - public static UInt64Field DestinationNode = int64Field(Field.DestinationNode); - public static UInt64Field Cookie = int64Field(Field.Cookie); - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - @Override - public Type type() { - return Type.UInt64; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt8.java b/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt8.java deleted file mode 100644 index 528eaa8614..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/coretypes/uint/UInt8.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.ripple.core.coretypes.uint; - -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; -import com.ripple.core.fields.UInt8Field; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.TypeTranslator; - -import java.math.BigInteger; - -public class UInt8 extends UInt { - public final static UInt8 ZERO = new UInt8(0); - - public static TypeTranslator translate = new UINTTranslator() { - @Override - public UInt8 newInstance(BigInteger i) { - return new UInt8(i); - } - - @Override - public int byteWidth() { - return 1; - } - }; - - public UInt8(byte[] bytes) { - super(bytes); - } - - public UInt8(BigInteger value) { - super(value); - } - - public UInt8(Number s) { - super(s); - } - - public UInt8(String s) { - super(s); - } - - public UInt8(String s, int radix) { - super(s, radix); - } - - public static UInt8 fromParser(BinaryParser parser) { - return translate.fromParser(parser); - } - public static UInt8 fromHex(String hex) { - return translate.fromHex(hex); - } - public static UInt8 fromBytes(byte[] bytes) { - return translate.fromBytes(bytes); - } - - @Override - public int getByteWidth() { - return 1; - } - - @Override - protected UInt8 instanceFrom(BigInteger n) { - return new UInt8(n); - } - - @Override - public Short value() { - return shortValue(); - } - - private static UInt8Field int8Field(final Field f) { - return new UInt8Field() {@Override public Field getField() {return f; } }; - } - - static public UInt8Field CloseResolution = int8Field(Field.CloseResolution); - static public UInt8Field Method = int8Field(Field.Method); - static public UInt8Field TransactionResult = int8Field(Field.TransactionResult); - static public UInt8Field TickSize = int8Field(Field.TickSize); - - @Override - public Object toJSON() { - return translate.toJSON(this); - } - - @Override - public byte[] toBytes() { - return translate.toBytes(this); - } - - @Override - public String toHex() { - return translate.toHex(this); - } - - @Override - public void toBytesSink(BytesSink to) { - translate.toBytesSink(this, to); - } - - @Override - public Type type() { - return Type.UInt8; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/enums/LedgerFlag.java b/ripple-core/src/main/java/com/ripple/core/enums/LedgerFlag.java deleted file mode 100644 index 99f18b5309..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/enums/LedgerFlag.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.ripple.core.enums; - -// Ledger Specific Flags -public class LedgerFlag { - public static int - // ltACCOUNT_ROOT - PasswordSpent = 0x00010000, // True, if password set fee is spent. - RequireDestTag = 0x00020000, // True, to require a DestinationTag for payments. - RequireAuth = 0x00040000, // True, to require a authorization to hold IOUs. - DisallowXRP = 0x00080000, // True, to disallow sending XRP. - DisableMaster = 0x00100000, // True, force regular key - NoFreeze = 0x00200000, // True, cannot freeze ripple states - GlobalFreeze = 0x00400000, // True, all assets frozen - DefaultRipple = 0x00800000, // True, all assets frozen - - // ltOFFER - Passive = 0x00010000, - Sell = 0x00020000, // True, offer was placed as a sell. - - // ltRIPPLE_STATE - LowReserve = 0x00010000, // True, if entry counts toward reserve. - HighReserve = 0x00020000, - LowAuth = 0x00040000, - HighAuth = 0x00080000, - LowNoRipple = 0x00100000, - HighNoRipple = 0x00200000, - LowFreeze = 0x00400000, // True, low side has set freeze flag - HighFreeze = 0x00800000; // True, high side has set freeze flag -} diff --git a/ripple-core/src/main/java/com/ripple/core/enums/TransactionFlag.java b/ripple-core/src/main/java/com/ripple/core/enums/TransactionFlag.java deleted file mode 100644 index adb7f9e910..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/enums/TransactionFlag.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.ripple.core.enums; - -// Transaction Specific Flags -public class TransactionFlag { - public static long - FullyCanonicalSig = 0x80000000L, - Universal = FullyCanonicalSig, - UniversalMask = ~Universal, - - // AccountSet flags: - RequireDestTag = 0x00010000, - OptionalDestTag = 0x00020000, - RequireAuth = 0x00040000, - OptionalAuth = 0x00080000, - DisallowXRP = 0x00100000, - AllowXRP = 0x00200000, - AccountSetMask = ~(Universal | RequireDestTag | OptionalDestTag - | RequireAuth | OptionalAuth - | DisallowXRP | AllowXRP), - - // AccountSet SetFlag/ClearFlag values - asfRequireDest = 1, - asfRequireAuth = 2, - asfDisallowXRP = 3, - asfDisableMaster = 4, - asfAccountTxnID = 5, - asfNoFreeze = 6, - asfGlobalFreeze = 7, - - // OfferCreate flags: - Passive = 0x00010000, - ImmediateOrCancel = 0x00020000, - FillOrKill = 0x00040000, - Sell = 0x00080000, - OfferCreateMask = ~(Universal | Passive | ImmediateOrCancel | FillOrKill | Sell), - - // Payment flags: - NoRippleDirect = 0x00010000, - PartialPayment = 0x00020000, - LimitQuality = 0x00040000, - PaymentMask = ~(Universal | PartialPayment | LimitQuality | NoRippleDirect), - - // TrustSet flags: - SetAuth = 0x00010000, - SetNoRipple = 0x00020000, - ClearNoRipple = 0x00040000, - SetFreeze = 0x00100000, - ClearFreeze = 0x00200000, - TrustSetMask = ~(Universal | SetAuth | SetNoRipple | ClearNoRipple | SetFreeze | ClearFreeze); -} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/AccountIDField.java b/ripple-core/src/main/java/com/ripple/core/fields/AccountIDField.java deleted file mode 100644 index f31e3df14d..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/AccountIDField.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class AccountIDField implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/AmountField.java b/ripple-core/src/main/java/com/ripple/core/fields/AmountField.java deleted file mode 100644 index 4666ff51f9..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/AmountField.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class AmountField implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/BlobField.java b/ripple-core/src/main/java/com/ripple/core/fields/BlobField.java deleted file mode 100644 index 2925c89611..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/BlobField.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class BlobField implements HasField{} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/Field.java b/ripple-core/src/main/java/com/ripple/core/fields/Field.java deleted file mode 100644 index 87871ecca0..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/Field.java +++ /dev/null @@ -1,328 +0,0 @@ -package com.ripple.core.fields; - -import java.util.*; - -public enum Field { - // These are all presorted (verified in a static block below) - // They can then be used in a TreeMap, using the Enum (private) ordinal - // comparator - Generic(0, Type.Unknown), - Invalid(-1, Type.Unknown), - - LedgerEntryType(1, Type.UInt16), - TransactionType(2, Type.UInt16), - SignerWeight(3, Type.UInt16), - - Flags(2, Type.UInt32), - SourceTag(3, Type.UInt32), - Sequence(4, Type.UInt32), - PreviousTxnLgrSeq(5, Type.UInt32), - LedgerSequence(6, Type.UInt32), - CloseTime(7, Type.UInt32), - ParentCloseTime(8, Type.UInt32), - SigningTime(9, Type.UInt32), - Expiration(10, Type.UInt32), - TransferRate(11, Type.UInt32), - WalletSize(12, Type.UInt32), - OwnerCount(13, Type.UInt32), - DestinationTag(14, Type.UInt32), - - HighQualityIn(16, Type.UInt32), - HighQualityOut(17, Type.UInt32), - LowQualityIn(18, Type.UInt32), - LowQualityOut(19, Type.UInt32), - QualityIn(20, Type.UInt32), - QualityOut(21, Type.UInt32), - StampEscrow(22, Type.UInt32), - BondAmount(23, Type.UInt32), - LoadFee(24, Type.UInt32), - OfferSequence(25, Type.UInt32), - FirstLedgerSequence(26, Type.UInt32), // Deprecated: do not use - // Added new semantics in 9486fc416ca7c59b8930b734266eed4d5b714c50 - LastLedgerSequence(27, Type.UInt32), - TransactionIndex(28, Type.UInt32), - OperationLimit(29, Type.UInt32), - ReferenceFeeUnits(30, Type.UInt32), - ReserveBase(31, Type.UInt32), - ReserveIncrement(32, Type.UInt32), - SetFlag(33, Type.UInt32), - ClearFlag(34, Type.UInt32), - SignerQuorum(35, Type.UInt32), - CancelAfter(36, Type.UInt32), - FinishAfter(37, Type.UInt32), - SignerListID(38, Type.UInt32), - SettleDelay(39, Type.UInt32), - - IndexNext(1, Type.UInt64), - IndexPrevious(2, Type.UInt64), - BookNode(3, Type.UInt64), - OwnerNode(4, Type.UInt64), - BaseFee(5, Type.UInt64), - ExchangeRate(6, Type.UInt64), - LowNode(7, Type.UInt64), - HighNode(8, Type.UInt64), - DestinationNode(9, Type.UInt64), - Cookie(10, Type.UInt64), - - EmailHash(1, Type.Hash128), - - LedgerHash(1, Type.Hash256), - ParentHash(2, Type.Hash256), - TransactionHash(3, Type.Hash256), - AccountHash(4, Type.Hash256), - PreviousTxnID(5, Type.Hash256), - LedgerIndex(6, Type.Hash256), - WalletLocator(7, Type.Hash256), - RootIndex(8, Type.Hash256), - // Added in rippled commit: 9486fc416ca7c59b8930b734266eed4d5b714c50 - AccountTxnID(9, Type.Hash256), - BookDirectory(16, Type.Hash256), - InvoiceID(17, Type.Hash256), - Nickname(18, Type.Hash256), - Amendment(19, Type.Hash256), - TicketID(20, Type.Hash256), - Digest(21, Type.Hash256), - Channel(22, Type.Hash256), - ConsensusHash(23, Type.Hash256), - CheckID(24, Type.Hash256), - - hash(257, Type.Hash256), - index(258, Type.Hash256), - - Amount(1, Type.Amount), - Balance(2, Type.Amount), - LimitAmount(3, Type.Amount), - TakerPays(4, Type.Amount), - TakerGets(5, Type.Amount), - LowLimit(6, Type.Amount), - HighLimit(7, Type.Amount), - Fee(8, Type.Amount), - SendMax(9, Type.Amount), - DeliverMin(10, Type.Amount), - - MinimumOffer(16, Type.Amount), - RippleEscrow(17, Type.Amount), - // Added in rippled commit: e7f0b8eca69dd47419eee7b82c8716b3aa5a9e39 - DeliveredAmount(18, Type.Amount), - // These are auxiliary fields -// quality(257, Type.AMOUNT), - taker_gets_funded(258, Type.Amount), - taker_pays_funded(259, Type.Amount), - - PublicKey(1, Type.Blob), - MessageKey(2, Type.Blob), - SigningPubKey(3, Type.Blob), - TxnSignature(4, Type.Blob), - Signature(6, Type.Blob), - Domain(7, Type.Blob), - FundCode(8, Type.Blob), - RemoveCode(9, Type.Blob), - ExpireCode(10, Type.Blob), - CreateCode(11, Type.Blob), - MemoType(12, Type.Blob), - MemoData(13, Type.Blob), - MemoFormat(14, Type.Blob), - Fulfillment(16, Type.Blob), - Condition(17, Type.Blob), - MasterSignature(18, Type.Blob), - - Account(1, Type.AccountID), - Owner(2, Type.AccountID), - Destination(3, Type.AccountID), - Issuer(4, Type.AccountID), - Authorize(5, Type.AccountID), - Unauthorize(6, Type.AccountID), - Target(7, Type.AccountID), - RegularKey(8, Type.AccountID), - - ObjectEndMarker(1, Type.STObject), - - TransactionMetaData(2, Type.STObject), - CreatedNode(3, Type.STObject), - DeletedNode(4, Type.STObject), - ModifiedNode(5, Type.STObject), - PreviousFields(6, Type.STObject), - FinalFields(7, Type.STObject), - NewFields(8, Type.STObject), - TemplateEntry(9, Type.STObject), - Memo(10, Type.STObject), - SignerEntry(11, Type.STObject), - Signer(16, Type.STObject), - // 17 unused - Majority(18, Type.STObject), - - ArrayEndMarker(1, Type.STArray), -// SigningAccounts(2, Type.STArray), - Signers(3, Type.STArray), - SignerEntries(4, Type.STArray), - Template(5, Type.STArray), - Necessary(6, Type.STArray), - Sufficient(7, Type.STArray), - AffectedNodes(8, Type.STArray), - Memos(9, Type.STArray), - Majorities(16, Type.STArray), - - CloseResolution(1, Type.UInt8), - Method(2, Type.UInt8), - TransactionResult(3, Type.UInt8), - TickSize(16, Type.UInt8), - - TakerPaysCurrency(1, Type.Hash160), - TakerPaysIssuer(2, Type.Hash160), - TakerGetsCurrency(3, Type.Hash160), - TakerGetsIssuer(4, Type.Hash160), - - Paths(1, Type.PathSet), - - Indexes(1, Type.Vector256), - Hashes(2, Type.Vector256), - Amendments(3, Type.Vector256), - - Transaction(1, Type.Transaction), - LedgerEntry(1, Type.LedgerEntry), - Validation(1, Type.Validation); - - final int id; - - // defaults - boolean signingField = true; - boolean isSerialized = true; - boolean isVlEncoded = false; - - public static Field fromString(String key) { - Field f; - try { - f = valueOf(key); - } catch (IllegalArgumentException e) { - f = null; - } - return f; - } - - private static byte[] asBytes(Field field) { - int name = field.getId(), type = field.getType().getId(); - ArrayList header = new ArrayList<>(3); - - if (type < 16) - { - if (name < 16) // common type, common name - header.add((byte)((type << 4) | name)); - else - { - // common type, uncommon name - header.add((byte)(type << 4)); - header.add((byte)(name)); - } - } - else if (name < 16) - { - // uncommon type, common name - header.add((byte)(name)); - header.add((byte)(type)); - } - else - { - // uncommon type, uncommon name - header.add((byte)(0)); - header.add((byte)(type)); - header.add((byte)(name)); - } - - byte[] headerBytes = new byte[header.size()]; - for (int i = 0; i < header.size(); i++) { - headerBytes[i] = header.get(i); - } - - return headerBytes; - } - - public int getId() { - return id; - } - - final int code; - final Type type; - private final byte[] bytes; - public Object tag = null; - - Field(int fid, Type tid) { - id = fid; - type = tid; - code = (type.id << 16) | fid; - isSerialized = isSerialized(this); - - if (isSerialized()) { - bytes = asBytes(this); - } else { - bytes = null; - } - } - - static private Map byCode = new TreeMap<>(); - - static public Field fromCode(Integer integer) { - return byCode.get(integer); - } - - public Type getType() { - return type; - } - - public boolean isSerialized() { - return isSerialized; - } - public boolean isVLEncoded() { - return isVlEncoded; - } - public boolean isSigningField() { - return signingField; - } - private static boolean isSerialized(Field f) { - // This should screen out `hash` and `index` and the like - return ((f.type.id > 0) && (f.type.id < 256) && (f.id > 0) && (f.id < 256)); - } - - static public Comparator comparator = Comparator.comparingInt(o -> o.code); - - static { - for (Field f : Field.values()) { - byCode.put(f.code, f); - f.isSerialized = isSerialized(f); - f.signingField = f.isSerialized; - - switch (f.type) { - case Blob: - case AccountID: - case Vector256: - f.isVlEncoded = true; - break; - default: - break; - } - - } - - TxnSignature.signingField = false; - Signers.signingField = false; - Signature.signingField = false; - MasterSignature.signingField = false; - - ArrayList sortedFields; - Field[] values = Field.values(); - sortedFields = new ArrayList<>(Arrays.asList(values)); - sortedFields.sort(comparator); - - for (int i = 0; i < values.length; i++) { - Field av = values[i]; - Field lv = sortedFields.get(i); - if (av.code != lv.code) { - throw new AssertionError( - "Field enum declaration isn't presorted"); - } - } - } - - public byte[] getBytes() { - return bytes; - } -} \ No newline at end of file diff --git a/ripple-core/src/main/java/com/ripple/core/fields/HasField.java b/ripple-core/src/main/java/com/ripple/core/fields/HasField.java deleted file mode 100644 index edc49029a3..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/HasField.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.ripple.core.fields; - -public interface HasField { - Field getField(); -} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/Hash128Field.java b/ripple-core/src/main/java/com/ripple/core/fields/Hash128Field.java deleted file mode 100644 index b8b78e912e..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/Hash128Field.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class Hash128Field implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/Hash160Field.java b/ripple-core/src/main/java/com/ripple/core/fields/Hash160Field.java deleted file mode 100644 index f0569b13eb..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/Hash160Field.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class Hash160Field implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/Hash256Field.java b/ripple-core/src/main/java/com/ripple/core/fields/Hash256Field.java deleted file mode 100644 index e0a83b67ef..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/Hash256Field.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class Hash256Field implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/PathSetField.java b/ripple-core/src/main/java/com/ripple/core/fields/PathSetField.java deleted file mode 100644 index 5982570eb6..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/PathSetField.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class PathSetField implements HasField{} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/STArrayField.java b/ripple-core/src/main/java/com/ripple/core/fields/STArrayField.java deleted file mode 100644 index 056a55c861..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/STArrayField.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class STArrayField implements HasField{} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/STObjectField.java b/ripple-core/src/main/java/com/ripple/core/fields/STObjectField.java deleted file mode 100644 index c802f57a2a..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/STObjectField.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class STObjectField implements HasField{} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/Type.java b/ripple-core/src/main/java/com/ripple/core/fields/Type.java deleted file mode 100644 index 2384861e88..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/Type.java +++ /dev/null @@ -1,35 +0,0 @@ -/* DO NOT EDIT, AUTO GENERATED */ -package com.ripple.core.fields; - -public enum Type { - Unknown(-2), - Done(-1), - NotPresent(0), - UInt16(1), - UInt32(2), - UInt64(3), - Hash128(4), - Hash256(5), - Amount(6), - Blob(7), - AccountID(8), - STObject(14), - STArray(15), - UInt8(16), - Hash160(17), - PathSet(18), - Vector256(19), - Transaction(10001), - LedgerEntry(10002), - Validation(10003); - - final int id; - - Type(int type) { - this.id = type; - } - - public int getId() { - return id; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/UInt16Field.java b/ripple-core/src/main/java/com/ripple/core/fields/UInt16Field.java deleted file mode 100644 index fa99de6499..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/UInt16Field.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class UInt16Field implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/UInt32Field.java b/ripple-core/src/main/java/com/ripple/core/fields/UInt32Field.java deleted file mode 100644 index a2b58902c2..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/UInt32Field.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class UInt32Field implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/UInt64Field.java b/ripple-core/src/main/java/com/ripple/core/fields/UInt64Field.java deleted file mode 100644 index 3c85f3b312..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/UInt64Field.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class UInt64Field implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/UInt8Field.java b/ripple-core/src/main/java/com/ripple/core/fields/UInt8Field.java deleted file mode 100644 index 465a1ad6be..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/UInt8Field.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class UInt8Field implements HasField {} diff --git a/ripple-core/src/main/java/com/ripple/core/fields/Vector256Field.java b/ripple-core/src/main/java/com/ripple/core/fields/Vector256Field.java deleted file mode 100644 index 7cadc88ea8..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/fields/Vector256Field.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.fields; - -public abstract class Vector256Field implements HasField{} diff --git a/ripple-core/src/main/java/com/ripple/core/formats/Format.java b/ripple-core/src/main/java/com/ripple/core/formats/Format.java deleted file mode 100644 index 0061213330..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/formats/Format.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.ripple.core.formats; - -import com.ripple.core.fields.Field; - -import java.util.EnumMap; - -abstract public class Format { - protected Format() { - } - - protected void addCommonFields(){} - - EnumMap requirementEnumMap = new EnumMap<>(Field.class); - EnumMap common = new EnumMap<>(Field.class); - - public EnumMap requirements() { - return requirementEnumMap; - } - - abstract public String name (); - - public Format(Object[] args) { - if ((!(args.length % 2 == 0)) || args.length < 2) { - throw new IllegalArgumentException("Varargs length should be a minimum multiple of 2"); - } - for (int i = 0; i < args.length; i+= 2) { - Field f = (Field) args[i]; - Requirement r = (Requirement) args[i + 1]; - put(f, r); - } - } - - protected void put(Field f, Requirement r) { - requirementEnumMap.put(f, r); - } - - @SuppressWarnings("unchecked") - Subclass required(Field f) { - put(f, Requirement.REQUIRED); - return (Subclass) this; - } - - @SuppressWarnings("unchecked") - Subclass optional(Field f) { - put(f, Requirement.OPTIONAL); - return (Subclass) this; - } - - @SuppressWarnings("unchecked") - Subclass nonDefault(Field f) { - put(f, Requirement.DEFAULT); - return (Subclass) this; - } - - public boolean isCommon(Field field) { - return common.containsKey(field); - } - - public static enum Requirement { - INVALID(-1), - REQUIRED( 0), - OPTIONAL( 1), - DEFAULT( 2); - Requirement(int i) {} - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/formats/LEFormat.java b/ripple-core/src/main/java/com/ripple/core/formats/LEFormat.java deleted file mode 100644 index 2dbd576593..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/formats/LEFormat.java +++ /dev/null @@ -1,230 +0,0 @@ -package com.ripple.core.formats; - -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.LedgerEntryType; - -import java.util.EnumMap; - -public class LEFormat extends Format { - static public EnumMap formats = new EnumMap(LedgerEntryType.class); - { - common.put(Field.LedgerIndex, Requirement.OPTIONAL); - common.put(Field.LedgerEntryType, Requirement.REQUIRED); - common.put(Field.Flags, Requirement.REQUIRED); - } - - static public LEFormat fromString(String name) { - return getLedgerFormat(LedgerEntryType.valueOf(name)); - } - - static public LEFormat fromNumber(Number ord) { - return getLedgerFormat(LedgerEntryType.fromNumber(ord)); - } - - static public LEFormat fromValue(Object o) { - if (o instanceof Number) { - return fromNumber(((Number) o).intValue()); - } else if (o instanceof String){ - return fromString((String) o); - } - else { - return null; - } - } - - public static LEFormat getLedgerFormat(LedgerEntryType key) { - if (key == null) return null; - return formats.get(key); - } - - public final LedgerEntryType ledgerEntryType; - - public LEFormat(LedgerEntryType type, Object... args) { - super(args); - ledgerEntryType = type; - addCommonFields(); - formats.put(type, this); - } - - public LEFormat(LedgerEntryType type) { - super(); - ledgerEntryType = type; - addCommonFields(); - formats.put(type, this); - } - - @Override - protected void addCommonFields() { - requirementEnumMap.putAll(common); - } - - @Override - public String name() { - return ledgerEntryType.toString(); - } - - public static LEFormat AccountRoot = new LEFormat( - LedgerEntryType.AccountRoot, - Field.Account, Requirement.REQUIRED, - Field.Sequence, Requirement.REQUIRED, - Field.Balance, Requirement.REQUIRED, - Field.OwnerCount, Requirement.REQUIRED, - Field.PreviousTxnID, Requirement.REQUIRED, - Field.PreviousTxnLgrSeq, Requirement.REQUIRED, - Field.AccountTxnID, Requirement.OPTIONAL, - Field.RegularKey, Requirement.OPTIONAL, - Field.EmailHash, Requirement.OPTIONAL, - Field.WalletLocator, Requirement.OPTIONAL, - Field.WalletSize, Requirement.OPTIONAL, - Field.MessageKey, Requirement.OPTIONAL, - Field.TransferRate, Requirement.OPTIONAL, - Field.Domain, Requirement.OPTIONAL, - Field.TickSize, Requirement.OPTIONAL - ); - - public static LEFormat DirectoryNode = new LEFormat( - LedgerEntryType.DirectoryNode, - Field.Owner, Requirement.OPTIONAL, // for owner directories - Field.TakerPaysCurrency, Requirement.OPTIONAL, // for order book directories - Field.TakerPaysIssuer, Requirement.OPTIONAL, // for order book directories - Field.TakerGetsCurrency, Requirement.OPTIONAL, // for order book directories - Field.TakerGetsIssuer, Requirement.OPTIONAL, // for order book directories - Field.ExchangeRate, Requirement.OPTIONAL, // for order book directories - Field.Indexes, Requirement.REQUIRED, - Field.RootIndex, Requirement.REQUIRED, - Field.IndexNext, Requirement.OPTIONAL, - Field.IndexPrevious, Requirement.OPTIONAL - ); - - - public static LEFormat Offer = new LEFormat( - LedgerEntryType.Offer, - Field.Account, Requirement.REQUIRED, - Field.Sequence, Requirement.REQUIRED, - Field.TakerPays, Requirement.REQUIRED, - Field.TakerGets, Requirement.REQUIRED, - Field.BookDirectory, Requirement.REQUIRED, - Field.BookNode, Requirement.REQUIRED, - Field.OwnerNode, Requirement.REQUIRED, - Field.PreviousTxnID, Requirement.REQUIRED, - Field.PreviousTxnLgrSeq, Requirement.REQUIRED, - Field.Expiration, Requirement.OPTIONAL - ); - - public static LEFormat Ticket = new LEFormat( - LedgerEntryType.Ticket, - Field.Account, Requirement.REQUIRED, - Field.Sequence, Requirement.REQUIRED, - Field.OwnerNode, Requirement.REQUIRED, - Field.Target, Requirement.OPTIONAL, - Field.Expiration, Requirement.OPTIONAL - ); - - public static LEFormat RippleState = new LEFormat( - LedgerEntryType.RippleState, - Field.Balance, Requirement.REQUIRED, - Field.LowLimit, Requirement.REQUIRED, - Field.HighLimit, Requirement.REQUIRED, - Field.PreviousTxnID, Requirement.REQUIRED, - Field.PreviousTxnLgrSeq, Requirement.REQUIRED, - Field.LowNode, Requirement.OPTIONAL, - Field.LowQualityIn, Requirement.OPTIONAL, - Field.LowQualityOut, Requirement.OPTIONAL, - Field.HighNode, Requirement.OPTIONAL, - Field.HighQualityIn, Requirement.OPTIONAL, - Field.HighQualityOut, Requirement.OPTIONAL - ); - - public static LEFormat Escrow = new LEFormat( - LedgerEntryType.Escrow, - Field.Account, Requirement.REQUIRED, - Field.Destination, Requirement.REQUIRED, - Field.Amount, Requirement.REQUIRED, - - Field.PreviousTxnID, Requirement.REQUIRED, - Field.PreviousTxnLgrSeq, Requirement.REQUIRED, - Field.OwnerNode, Requirement.REQUIRED, - - Field.Condition, Requirement.OPTIONAL, - Field.CancelAfter, Requirement.OPTIONAL, - Field.FinishAfter, Requirement.OPTIONAL, - Field.SourceTag, Requirement.OPTIONAL, - Field.DestinationTag, Requirement.OPTIONAL, - Field.DestinationNode, Requirement.OPTIONAL - ); - - public static LEFormat LedgerHashes = new LEFormat( - LedgerEntryType.LedgerHashes, - Field.FirstLedgerSequence, Requirement.OPTIONAL, // Remove if we do a ledger restart - Field.LastLedgerSequence, Requirement.OPTIONAL, - Field.Hashes, Requirement.REQUIRED - ); - - public static LEFormat Amendments = new LEFormat( - LedgerEntryType.Amendments, - Field.Amendments, Requirement.OPTIONAL, - Field.Majorities, Requirement.OPTIONAL - ); - - public static LEFormat SignerList = new LEFormat( - LedgerEntryType.SignerList, - - Field.PreviousTxnID, Requirement.REQUIRED, - Field.PreviousTxnLgrSeq, Requirement.REQUIRED, - Field.OwnerNode, Requirement.REQUIRED, - - Field.SignerQuorum, Requirement.REQUIRED, - Field.SignerEntries, Requirement.REQUIRED, - Field.SignerListID, Requirement.REQUIRED - ); - - public static LEFormat FeeSettings = new LEFormat( - LedgerEntryType.FeeSettings, - Field.BaseFee, Requirement.REQUIRED, - Field.ReferenceFeeUnits, Requirement.REQUIRED, - Field.ReserveBase, Requirement.REQUIRED, - Field.ReserveIncrement, Requirement.REQUIRED - ); - - public static LEFormat PayChannel = new LEFormat( - LedgerEntryType.PayChannel, - Field.Account, Requirement.REQUIRED, - Field.Destination, Requirement.REQUIRED, - Field.Amount, Requirement.REQUIRED, - Field.Balance, Requirement.REQUIRED, - Field.PublicKey, Requirement.REQUIRED, - Field.SettleDelay, Requirement.REQUIRED, - Field.Expiration, Requirement.OPTIONAL, - Field.CancelAfter, Requirement.OPTIONAL, - Field.SourceTag, Requirement.OPTIONAL, - Field.DestinationTag, Requirement.OPTIONAL, - Field.OwnerNode, Requirement.REQUIRED, - Field.PreviousTxnID, Requirement.REQUIRED, - Field.PreviousTxnLgrSeq, Requirement.REQUIRED - ); - - public static LEFormat Check = new LEFormat(LedgerEntryType.Check) - .required(Field.PreviousTxnID) - .required(Field.PreviousTxnLgrSeq) - .required(Field.Account) - .required(Field.Destination) - .required(Field.SendMax) - .required(Field.Sequence) - .required(Field.OwnerNode) - .required(Field.DestinationNode) - .optional(Field.Expiration) - .optional(Field.InvoiceID) - .optional(Field.SourceTag) - .optional(Field.DestinationTag) - - ; - - public static LEFormat DepositPreauth = new LEFormat(LedgerEntryType.DepositPreauth) - .required(Field.Account) - .required(Field.Authorize) - .required(Field.OwnerNode) - .required(Field.PreviousTxnID) - .required(Field.PreviousTxnLgrSeq) - ; - -} diff --git a/ripple-core/src/main/java/com/ripple/core/formats/TxFormat.java b/ripple-core/src/main/java/com/ripple/core/formats/TxFormat.java deleted file mode 100644 index fa66d27533..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/formats/TxFormat.java +++ /dev/null @@ -1,212 +0,0 @@ -package com.ripple.core.formats; - -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.TransactionType; - -import java.util.EnumMap; - -public class TxFormat extends Format { - static public EnumMap formats = new EnumMap<>(TransactionType.class); - { - common.put(Field.TransactionType, Requirement.REQUIRED); - common.put(Field.Account, Requirement.REQUIRED); - common.put(Field.Sequence, Requirement.REQUIRED); - common.put(Field.Fee, Requirement.REQUIRED); - common.put(Field.SigningPubKey, Requirement.REQUIRED); - - common.put(Field.Flags, Requirement.OPTIONAL); - common.put(Field.SourceTag, Requirement.OPTIONAL); - common.put(Field.PreviousTxnID, Requirement.OPTIONAL); - common.put(Field.OperationLimit, Requirement.OPTIONAL); - common.put(Field.TxnSignature, Requirement.OPTIONAL); - common.put(Field.AccountTxnID, Requirement.OPTIONAL); - common.put(Field.LastLedgerSequence, Requirement.OPTIONAL); - common.put(Field.Memos, Requirement.OPTIONAL); - common.put(Field.Signers, Requirement.OPTIONAL); - - } - public final TransactionType transactionType; - - static public TxFormat fromString(String name) { - return getTxFormat(TransactionType.valueOf(name)); - } - - static public TxFormat fromNumber(Number ord) { - return getTxFormat(TransactionType.fromNumber(ord)); - } - - private static TxFormat getTxFormat(TransactionType key) { - if (key == null) return null; - return formats.get(key); - } - - public TxFormat(TransactionType type, Object... args) { - super(args); - transactionType = type; - addCommonFields(); - formats.put(transactionType, this); - } - - public TxFormat(TransactionType type) { - super(); - transactionType = type; - addCommonFields(); - formats.put(transactionType, this); - } - - @Override - protected void addCommonFields() { - requirementEnumMap.putAll(common); - } - - @Override - public String name() { - return transactionType.toString(); - } - - private static TxFormat makeFormat(TransactionType tt) { - return new TxFormat(tt); - } - - static public TxFormat AccountSet = makeFormat( - TransactionType.AccountSet) - .optional(Field.EmailHash) - .optional(Field.EmailHash) - .optional(Field.WalletLocator) - .optional(Field.WalletSize) - .optional(Field.MessageKey) - .optional(Field.Domain) - .optional(Field.TransferRate) - .optional(Field.SetFlag) - .optional(Field.TickSize) - .optional(Field.ClearFlag); - - static public TxFormat TrustSet = new TxFormat( - TransactionType.TrustSet, - Field.LimitAmount, Requirement.OPTIONAL, - Field.QualityIn, Requirement.OPTIONAL, - Field.QualityOut, Requirement.OPTIONAL); - - static public TxFormat OfferCreate = new TxFormat( - TransactionType.OfferCreate, - Field.TakerPays, Requirement.REQUIRED, - Field.TakerGets, Requirement.REQUIRED, - Field.Expiration, Requirement.OPTIONAL, - Field.OfferSequence, Requirement.OPTIONAL); - - static public TxFormat OfferCancel = new TxFormat( - TransactionType.OfferCancel, - Field.OfferSequence, Requirement.REQUIRED); - - static public TxFormat TicketCreate = new TxFormat( - TransactionType.TicketCreate, - Field.Target, Requirement.OPTIONAL, - Field.Expiration, Requirement.OPTIONAL); - - static public TxFormat TicketCancel = new TxFormat( - TransactionType.TicketCancel, - Field.TicketID, Requirement.REQUIRED); - - static public TxFormat SetRegularKey = new TxFormat( - TransactionType.SetRegularKey, - Field.RegularKey, Requirement.OPTIONAL); - - static public TxFormat Payment = new TxFormat( - TransactionType.Payment, - Field.Destination, Requirement.REQUIRED, - Field.Amount, Requirement.REQUIRED, - Field.SendMax, Requirement.OPTIONAL, - Field.Paths, Requirement.DEFAULT, - Field.InvoiceID, Requirement.OPTIONAL, - Field.DestinationTag, Requirement.OPTIONAL, - Field.DeliverMin, Requirement.OPTIONAL - ); - - - static public TxFormat EscrowCreate = new TxFormat( - TransactionType.EscrowCreate, - Field.Destination, Requirement.REQUIRED, - Field.Amount, Requirement.REQUIRED, - Field.Condition, Requirement.OPTIONAL, - Field.CancelAfter, Requirement.OPTIONAL, - Field.FinishAfter, Requirement.OPTIONAL, - Field.DestinationTag, Requirement.OPTIONAL); - - static public TxFormat EscrowFinish = new TxFormat( - TransactionType.EscrowFinish, - Field.Owner, Requirement.REQUIRED, - Field.OfferSequence, Requirement.REQUIRED, - Field.Fulfillment, Requirement.OPTIONAL, - Field.Condition, Requirement.OPTIONAL); - - static public TxFormat EscrowCancel = new TxFormat( - TransactionType.EscrowCancel, - Field.Owner, Requirement.REQUIRED, - Field.OfferSequence, Requirement.REQUIRED); - - static public TxFormat EnableAmendment = new TxFormat( - TransactionType.EnableAmendment, - Field.LedgerSequence, Requirement.REQUIRED, - Field.Amendment, Requirement.REQUIRED); - - static public TxFormat SetFee = new TxFormat( - TransactionType.SetFee, - Field.BaseFee, Requirement.REQUIRED, - Field.ReferenceFeeUnits, Requirement.REQUIRED, - Field.ReserveBase, Requirement.REQUIRED, - Field.LedgerSequence, Requirement.OPTIONAL, - Field.ReserveIncrement, Requirement.REQUIRED - ); - - static public TxFormat SignerListSet = new TxFormat( - TransactionType.SignerListSet, - Field.SignerQuorum, Requirement.REQUIRED, - Field.SignerEntries, Requirement.OPTIONAL - ); - static public TxFormat PaymentChannelCreate = new TxFormat( - TransactionType.PaymentChannelCreate, - Field.Destination, Requirement.REQUIRED, - Field.Amount, Requirement.REQUIRED, - Field.SettleDelay, Requirement.REQUIRED, - Field.PublicKey, Requirement.REQUIRED, - Field.CancelAfter, Requirement.OPTIONAL, - Field.DestinationTag, Requirement.OPTIONAL - ); - static public TxFormat PaymentChannelFund = new TxFormat( - TransactionType.PaymentChannelFund, - Field.Channel, Requirement.REQUIRED, - Field.Amount, Requirement.REQUIRED, - Field.Expiration, Requirement.OPTIONAL - ); - static public TxFormat PaymentChannelClaim = new TxFormat( - TransactionType.PaymentChannelClaim, - Field.Channel, Requirement.REQUIRED, - Field.Amount, Requirement.OPTIONAL, - Field.Balance, Requirement.OPTIONAL, - Field.Signature, Requirement.OPTIONAL, - Field.PublicKey, Requirement.OPTIONAL - ); - - static public TxFormat CheckCreate = new TxFormat(TransactionType.CheckCreate) - .required(Field.Destination) - .required(Field.SendMax) - .optional(Field.Expiration) - .optional(Field.DestinationTag) - .optional(Field.InvoiceID) - ; - - static public TxFormat CheckCash = new TxFormat(TransactionType.CheckCash) - .required(Field.CheckID) - .optional(Field.Amount) - .optional(Field.DeliverMin) - ; - - static public TxFormat CheckCancel = new TxFormat(TransactionType.CheckCancel) - .required(Field.CheckID) - ; - - static public TxFormat DepositPreauth = new TxFormat(TransactionType.DepositPreauth) - .optional(Field.Authorize) - .optional(Field.Unauthorize) - ; -} diff --git a/ripple-core/src/main/java/com/ripple/core/runtime/Value.java b/ripple-core/src/main/java/com/ripple/core/runtime/Value.java deleted file mode 100644 index 1f69e88c55..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/runtime/Value.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.ripple.core.runtime; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeType; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.util.List; -import java.util.Map; - -public enum Value { - UNKNOWN, - STRING, - JSON_OBJECT, - JSON_ARRAY, - - JACKSON_ARRAY, - JACKSON_BINARY, - JACKSON_BOOLEAN, - JACKSON_MISSING, - JACKSON_NULL, - JACKSON_NUMBER, - JACKSON_OBJECT, - JACKSON_POJO, - JACKSON_STRING, - - LIST, - MAP, - NUMBER, - BYTE, - DOUBLE, - FLOAT, - INTEGER, - LONG, - BYTE_ARRAY, - SHORT, - BOOLEAN; - - static public Value typeOf (Object object) { - if (object instanceof String) { - return STRING; - } - else if (object instanceof Number) { - if (object instanceof Byte) { - return BYTE; - } - else if (object instanceof Double) { - return DOUBLE; - } - else if (object instanceof Float) { - return FLOAT; - } - else if (object instanceof Integer) { - return INTEGER; - } - else if (object instanceof Long) { - return LONG; - } - else if (object instanceof Short) { - return SHORT; - } - return NUMBER; - } - else if (object instanceof JSONObject) { - return JSON_OBJECT; - } - else if (object instanceof JSONArray) { - return JSON_ARRAY; - } - else if (object instanceof JsonNode) { - JsonNode node = (JsonNode) object; - JsonNodeType nodeType = node.getNodeType(); - switch (nodeType) { - case ARRAY: - return JACKSON_ARRAY; - case BINARY: - return JACKSON_BINARY; - case BOOLEAN: - return JACKSON_BOOLEAN; - case MISSING: - return JACKSON_MISSING; - case NULL: - return JACKSON_NULL; - case NUMBER: - return JACKSON_NUMBER; - case OBJECT: - return JACKSON_OBJECT; - case POJO: - return JACKSON_POJO; - case STRING: - return JACKSON_STRING; - } - throw new IllegalStateException("unknown node type"); - } - else if (object instanceof Map) { - return MAP; - } - else if (object instanceof Boolean) { - return BOOLEAN; - } - else if (object instanceof List) { - return LIST; - } - else if (object instanceof byte[]) { - return BYTE_ARRAY; - } - else { - return UNKNOWN; - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/BinaryParser.java b/ripple-core/src/main/java/com/ripple/core/serialized/BinaryParser.java deleted file mode 100644 index 86fa08a7d9..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/BinaryParser.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.ripple.core.serialized; - -import com.ripple.core.fields.Field; -import com.ripple.encodings.common.B16; - -public class BinaryParser { - protected final int size; - protected byte[] bytes; - protected int cursor = 0; - - public BinaryParser(byte[] bytes) { - this.size = bytes.length; - this.bytes = bytes; - } - - public BinaryParser(int size) { - this.size = size; - } - - public BinaryParser(String hex) { - this(B16.decode(hex)); - } - - public void skip(int n) { - cursor += n; - } - public byte readOne() { - return bytes[cursor++]; - } - public byte[] read(int n) { - byte[] ret = new byte[n]; - System.arraycopy(bytes, cursor, ret, 0, n); - cursor += n; -// } - return ret; - } - - public Field readField() { - int fieldCode = readFieldCode(); - Field field = Field.fromCode(fieldCode); - if (field == null) { - throw new IllegalStateException("Couldn't parse field from " + - Integer.toHexString(fieldCode)); - } - return field; - } - - public int readFieldCode() { - byte tagByte = readOne(); - - int typeBits = (tagByte & 0xFF) >>> 4; - if (typeBits == 0) typeBits = readOne(); - - int fieldBits = tagByte & 0x0F; - if (fieldBits == 0) fieldBits = readOne(); - - return (typeBits << 16 | fieldBits); - } - - public boolean end() { - return cursor >= size; // greater guard against infinite loops - } - - public int pos() { - return cursor; - } - - public int readOneInt() { - return readOne() & 0xFF; - } - - public int readVLLength() { - int b1 = readOneInt(); - int result; - - if (b1 <= 192) { - result = b1; - } else if (b1 <= 240) { - int b2 = readOneInt(); - result = 193 + (b1 - 193) * 256 + b2; - } else if (b1 <= 254) { - int b2 = readOneInt(); - int b3 = readOneInt(); - result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3; - } else { - throw new RuntimeException("Invalid varint length indicator"); - } - - return result; - } - - public int size() { - return size; - } - - public boolean end(Integer customEnd) { - return cursor >= size || customEnd != null && cursor >= customEnd; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/BinarySerializer.java b/ripple-core/src/main/java/com/ripple/core/serialized/BinarySerializer.java deleted file mode 100644 index 1b39a2ac10..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/BinarySerializer.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.ripple.core.serialized; - -import com.ripple.core.fields.Field; -import com.ripple.core.fields.Type; - -import java.util.Arrays; - -public class BinarySerializer { - private final BytesSink sink; - - public BinarySerializer(BytesSink sink) { - this.sink = sink; - } - - public void add(byte[] n) { - sink.add(n); - } - - public void addLengthEncoded(byte[] n) { - add(encodeVL(n.length)); - add(n); - } - - public static byte[] encodeVL(int length) { - // TODO: bytes - byte[] lenBytes = new byte[4]; - - if (length <= 192) - { - lenBytes[0] = (byte) (length); - return Arrays.copyOf(lenBytes, 1); - } - else if (length <= 12480) - { - length -= 193; - lenBytes[0] = (byte) (193 + (length >>> 8)); - lenBytes[1] = (byte) (length & 0xff); - return Arrays.copyOf(lenBytes, 2); - } - else if (length <= 918744) { - length -= 12481; - lenBytes[0] = (byte) (241 + (length >>> 16)); - lenBytes[1] = (byte) ((length >> 8) & 0xff); - lenBytes[2] = (byte) (length & 0xff); - return Arrays.copyOf(lenBytes, 3); - } else { - throw new RuntimeException("Overflow error"); - } - } - - public void add(BytesList bl) { - for (byte[] bytes : bl.rawList()) { - sink.add(bytes); - } - } - - public int addFieldHeader(Field f) { - if (!f.isSerialized()) { - throw new IllegalStateException(String.format("Field %s is a discardable field", f)); - } - byte[] n = f.getBytes(); - add(n); - return n.length; - } - - public void add(byte type) { - sink.add(type); - } - - public void addLengthEncoded(BytesList bytes) { - add(encodeVL(bytes.bytesLength())); - add(bytes); - } - - public void add(Field field, SerializedType value) { - addFieldHeader(field); - if (field.isVLEncoded()) { - addLengthEncoded(value); - } else { - value.toBytesSink(sink); - if (field.getType() == Type.STObject) { - addFieldHeader(Field.ObjectEndMarker); - } else if (field.getType() == Type.STArray) { - addFieldHeader(Field.ArrayEndMarker); - } - } - } - - public void addLengthEncoded(SerializedType value) { - BytesList bytes = new BytesList(); - value.toBytesSink(bytes); - addLengthEncoded(bytes); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/BytesList.java b/ripple-core/src/main/java/com/ripple/core/serialized/BytesList.java deleted file mode 100644 index 69d150aa16..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/BytesList.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.ripple.core.serialized; - -import java.security.MessageDigest; -import java.util.ArrayList; - -public class BytesList implements BytesSink { - private ArrayList buffer = new ArrayList(); - - private int len = 0; - - public void add(BytesList bl) { - for (byte[] bytes : bl.rawList()) { - add(bytes); - } - } - - @Override - public void add(byte aByte) { - add(new byte[]{aByte}); - } - - @Override - public void add(byte[] bytes) { - len += bytes.length; - buffer.add(bytes); - } - - public byte[] bytes() { - int n = bytesLength(); - byte[] bytes = new byte[n]; - addBytes(bytes, 0); - return bytes; - } - - static public String[] hexLookup = new String[256]; - static { - for (int i = 0; i < 256; i++) { - String s = Integer.toHexString(i).toUpperCase(); - if (s.length() == 1) { - s = "0" + s; - } - hexLookup[i] = s; - } - } - - public String bytesHex() { - StringBuilder builder = new StringBuilder(len * 2); - for (byte[] buf : buffer) { - for (byte aBytes : buf) { - builder.append(hexLookup[aBytes & 0xFF]); - } - } - return builder.toString(); - } - - public int bytesLength() { - return len; - } - - private int addBytes(byte[] bytes, int destPos) { - for (byte[] buf : buffer) { - System.arraycopy(buf, 0, bytes, destPos, buf.length); - destPos += buf.length; - } - return destPos; - } - - public void updateDigest(MessageDigest digest) { - for (byte[] buf : buffer) { - digest.update(buf); - } - } - - public ArrayList rawList() { - return buffer; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/BytesSink.java b/ripple-core/src/main/java/com/ripple/core/serialized/BytesSink.java deleted file mode 100644 index 7ec5c6cc5a..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/BytesSink.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.ripple.core.serialized; - -public interface BytesSink { - default void add(byte aByte) { - add(new byte[] {aByte}); - } - void add(byte[] bytes); -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/MultiSink.java b/ripple-core/src/main/java/com/ripple/core/serialized/MultiSink.java deleted file mode 100644 index 02de05e311..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/MultiSink.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ripple.core.serialized; - -public class MultiSink implements BytesSink { - final private BytesSink[] sinks; - public MultiSink(BytesSink... sinks) { - this.sinks = sinks; - } - @Override - public void add(byte b) { - for (BytesSink sink : sinks) sink.add(b); - } - @Override - public void add(byte[] b) { - for (BytesSink sink : sinks) sink.add(b); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/SerializedType.java b/ripple-core/src/main/java/com/ripple/core/serialized/SerializedType.java deleted file mode 100644 index 1b5263e935..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/SerializedType.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ripple.core.serialized; - -import com.ripple.core.fields.Type; - -public interface SerializedType { - Object toJSON(); - byte[] toBytes(); - String toHex(); - void toBytesSink(BytesSink to); - Type type(); -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/StreamBinaryParser.java b/ripple-core/src/main/java/com/ripple/core/serialized/StreamBinaryParser.java deleted file mode 100644 index b3f964c31d..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/StreamBinaryParser.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.ripple.core.serialized; - -import java.io.*; -import java.util.zip.GZIPInputStream; - -public class StreamBinaryParser extends BinaryParser { - final BufferedInputStream stream; - - public StreamBinaryParser(InputStream stream, long size) { - super((int) size); - this.stream = new BufferedInputStream(stream); - } - - private static boolean isGZip(File fio) { - return fio.getName().endsWith("gz"); - } - private static int getUncompressedSize(File fio) { - if (isGZip(fio)) { - int val; - try { - RandomAccessFile raf = new RandomAccessFile(fio, "r"); - raf.seek(raf.length() - 4); - int b4 = raf.read(); - int b3 = raf.read(); - int b2 = raf.read(); - int b1 = raf.read(); - val = (b1 << 24) | (b2 << 16) + (b3 << 8) + b4; - raf.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - return val; - } else { - return (int) fio.length(); - } - } - - public void skip(int n) { - try { - long skipped = stream.skip(n); - if (skipped != n) { - throw new RuntimeException("Expected to skip more bytes"); - } - - } catch (IOException e) { - throw new RuntimeException(e); - } - } - public byte readOne() { - return read(1)[0]; - } - public byte[] read(int n) { - byte[] ret = new byte[n]; - try { - int read = stream.read(ret); - if (read != n) { - throw new RuntimeException("Expected to read more bytes"); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - cursor += n; - return ret; - } - public static StreamBinaryParser fromFile(String path) { - try { - File f = new File(path); - FileInputStream fstream = new FileInputStream(path); - InputStream stream = fstream; - long s = fstream.getChannel().size(); - - if (isGZip(f)) { - s = getUncompressedSize(f); - stream = new GZIPInputStream(fstream); - } - return new StreamBinaryParser(stream, s); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/StreamSink.java b/ripple-core/src/main/java/com/ripple/core/serialized/StreamSink.java deleted file mode 100644 index 34fe16d377..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/StreamSink.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.ripple.core.serialized; - -import java.io.IOException; -import java.io.OutputStream; - -public class StreamSink implements BytesSink { - OutputStream out; - - public StreamSink(OutputStream out) { - this.out = out; - } - - @Override - public void add(byte aByte) { - try { - out.write(aByte); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public void add(byte[] bytes) { - try { - out.write(bytes); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} - diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/TypeTranslator.java b/ripple-core/src/main/java/com/ripple/core/serialized/TypeTranslator.java deleted file mode 100644 index 4bd988338b..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/TypeTranslator.java +++ /dev/null @@ -1,151 +0,0 @@ - -package com.ripple.core.serialized; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ripple.core.runtime.Value; -import com.ripple.encodings.common.B16; -import org.json.JSONArray; -import org.json.JSONObject; - -/** - * @param The SerializedType class - * TODO, this should only really have methods that each class over-rides - * it's currently pretty NASTY - */ -public abstract class TypeTranslator { - - @SuppressWarnings("unchecked") - public T fromValue(Object object) { - switch (Value.typeOf(object)) { - case STRING: - return fromString((String) object); - case JACKSON_ARRAY: - return fromJacksonArray((ArrayNode) object); - case JACKSON_BINARY: - throw new IllegalStateException("cant handle jackson binary"); - case JACKSON_BOOLEAN: - return fromBoolean(((JsonNode) object).asBoolean()); - case JACKSON_MISSING: - throw new IllegalStateException("cant create from missing"); - case JACKSON_NULL: - throw new IllegalStateException("cant create from null"); - case JACKSON_NUMBER: - JsonNode node = (JsonNode) object; - if (node.isLong()) { - return fromLong(node.asLong()); - } else if (node.isInt()) { - return fromInteger(node.asInt()); - } else if (node.isDouble()) { - return fromDouble(node.asDouble()); - } else if (node.isFloat()) { - return fromDouble(node.floatValue()); - } - throw new IllegalStateException("cant create from null"); - case JACKSON_OBJECT: - return fromJacksonObject((ObjectNode) object); - case JACKSON_POJO: - throw new IllegalStateException("cant handle POJO"); - case JACKSON_STRING: - return fromString(((JsonNode) object).asText()); - case DOUBLE: - return fromDouble((Double) object); - case INTEGER: - return fromInteger((Integer) object); - case LONG: - return fromLong((Long) object); - case BOOLEAN: - return fromBoolean((Boolean) object); - case JSON_ARRAY: - return fromJSONArray((JSONArray) object); - case JSON_OBJECT: - return fromJSONObject((JSONObject) object); - case BYTE_ARRAY: - return fromBytes((byte[]) object); - case UNKNOWN: - default: - return (T) object; - } - - } - - protected T fromJacksonObject(ObjectNode object) { - throw new UnsupportedOperationException(); - } - - protected T fromJacksonArray(ArrayNode node) { - throw new UnsupportedOperationException(); - } - - public String toString(T obj) { - return obj.toString(); - } - - protected T fromJSONObject(JSONObject jsonObject) { - throw new UnsupportedOperationException(); - } - - protected T fromJSONArray(JSONArray jsonArray) { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("WeakerAccess") - protected T fromBoolean(boolean aBoolean) { - throw new UnsupportedOperationException(); - } - - protected T fromLong(long aLong) { - throw new UnsupportedOperationException(); - } - - protected T fromInteger(int integer) { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("WeakerAccess") - protected T fromDouble(double aDouble) { - throw new UnsupportedOperationException(); - } - - protected T fromString(String value) { - throw new UnsupportedOperationException(); - } - - /** - * @param hint Using a boxed integer, allowing null for no hint - */ - public abstract T fromParser(BinaryParser parser, Integer hint); - - public T fromParser(BinaryParser parser) { - return fromParser(parser, null); - } - - public T fromBytes(byte[] b) { - return fromParser(new BinaryParser(b)); - } - - public T fromHex(String hex) { - return fromBytes(B16.decode(hex)); - } - - public Object toJSON(T obj) { - return obj.toJSON(); - } - - public void toBytesSink(T obj, BytesSink to) { - obj.toBytesSink(to); - } - - public byte[] toBytes(T obj) { - BytesList to = new BytesList(); - toBytesSink(obj, to); - return to.bytes(); - } - - public String toHex(T obj) { - BytesList to = new BytesList(); - toBytesSink(obj, to); - return to.bytesHex(); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/enums/EngineResult.java b/ripple-core/src/main/java/com/ripple/core/serialized/enums/EngineResult.java deleted file mode 100644 index dafd5d761a..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/enums/EngineResult.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.ripple.core.serialized.enums; - -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import com.ripple.encodings.common.B16; - -import java.util.TreeMap; - -class Holder { - public static int ords = 0; -} - -public enum EngineResult implements SerializedType { - telLOCAL_ERROR(-399, "Local failure."), - telBAD_DOMAIN("Domain too long."), - telBAD_PATH_COUNT("Malformed: Too many paths."), - telBAD_PUBLIC_KEY("Public key too long."), - telFAILED_PROCESSING("Failed to correctly process transaction."), - telINSUF_FEE_P("Fee insufficient."), - telNO_DST_PARTIAL("Partial payment to create account not allowed."), - - telCAN_NOT_QUEUE("Can not queue at this time."), - telCAN_NOT_QUEUE_BALANCE("Can not queue at this time: insufficient balance to pay all queued fees."), - telCAN_NOT_QUEUE_BLOCKS("Can not queue at this time: would block later queued transaction(s)."), - - telCAN_NOT_QUEUE_BLOCKED("Can not queue at this time: blocking transaction in queue."), - telCAN_NOT_QUEUE_FEE("Can not queue at this time: fee insufficient to replace queued transaction."), - telCAN_NOT_QUEUE_FULL("Can not queue at this time: queue is full."), - - temMALFORMED(-299, "Malformed transaction."), - temBAD_AMOUNT("Can only send positive amounts."), - temBAD_CURRENCY("Malformed: Bad currency."), - temBAD_EXPIRATION("Malformed: Bad expiration."), - temBAD_FEE("Invalid fee, negative or not XRP."), - temBAD_ISSUER("Malformed: Bad issuer."), - temBAD_LIMIT("Limits must be non-negative."), - temBAD_OFFER("Malformed: Bad offer."), - temBAD_PATH("Malformed: Bad path."), - temBAD_PATH_LOOP("Malformed: Loop in path."), - temBAD_SEND_XRP_LIMIT("Malformed: Limit quality is not allowed for XRP to XRP."), - temBAD_SEND_XRP_MAX("Malformed: Send max is not allowed for XRP to XRP."), - temBAD_SEND_XRP_NO_DIRECT("Malformed: No Ripple direct is not allowed for XRP to XRP."), - temBAD_SEND_XRP_PARTIAL("Malformed: Partial payment is not allowed for XRP to XRP."), - temBAD_SEND_XRP_PATHS("Malformed: Paths are not allowed for XRP to XRP."), - temBAD_SEQUENCE("Malformed: Sequence is not in the past."), - temBAD_SIGNATURE("Malformed: Bad signature."), - temBAD_SRC_ACCOUNT("Malformed: Bad source account."), - temBAD_TRANSFER_RATE("Malformed: Transfer rate must be >= 1.0 and <= 2.0"), - temDST_IS_SRC("Destination may not be source."), - temDST_NEEDED("Destination not specified."), - temINVALID("The transaction is ill-formed."), - temINVALID_FLAG("The transaction has an invalid flag."), - temREDUNDANT("Sends same currency to self."), - temRIPPLE_EMPTY("PathSet with no paths."), - temDISABLED("The transaction requires logic that is currently disabled."), - temBAD_SIGNER("Malformed: No signer may duplicate account or other signers."), - temBAD_QUORUM("Malformed: Quorum is unreachable."), - temBAD_WEIGHT("Malformed: Weight must be a positive value."), - temBAD_TICK_SIZE("Malformed: Tick size out of range."), - temINVALID_ACCOUNT_ID("Malformed: A field contains an invalid account ID."), - temCANNOT_PREAUTH_SELF("Malformed: An account may not preauthorize itself."), - temUNCERTAIN("In process of determining result. Never returned."), - temUNKNOWN(-266, "The transaction requires logic that is not implemented yet."), - - tefFAILURE(-199, "Failed to apply."), - tefALREADY("The exact transaction was already in this ledger."), - tefBAD_ADD_AUTH("Not authorized to add account."), - tefBAD_AUTH("Transaction's public key is not authorized."), - tefBAD_LEDGER("Ledger in unexpected state."), - tefCREATED("Can't add an already created account."), - tefEXCEPTION("Unexpected program state."), - tefINTERNAL("Internal error."), - tefNO_AUTH_REQUIRED("Auth is not required."), - tefPAST_SEQ("This sequence number has already passed."), - tefWRONG_PRIOR("This previous transaction does not match."), - tefMASTER_DISABLED("Master key is disabled."), - tefMAX_LEDGER("Ledger sequence too high."), - tefBAD_SIGNATURE("A signature is provided for a non-signer."), - tefBAD_QUORUM("Signatures provided do not meet the quorum."), - tefNOT_MULTI_SIGNING("Account has no appropriate list of multi-signers."), - tefBAD_AUTH_MASTER("Auth for unclaimed account needs correct master key."), - tefINVARIANT_FAILED("Fee claim violated invariants for the transaction."), - - - - - terRETRY(-99, "Retry transaction."), - terFUNDS_SPENT("Can't set password, password set funds already spent."), - terINSUF_FEE_B("Account balance can't pay fee."), - terNO_ACCOUNT("The source account does not exist."), - terNO_AUTH("Not authorized to hold IOUs."), - terNO_LINE("No such line."), - terOWNERS("Non-zero owner count."), - terPRE_SEQ("Missing/inapplicable prior transaction."), - terLAST("Process last."), - terNO_RIPPLE("Path does not permit rippling."), - terQUEUED("Held until escalated fee drops."), - - tesSUCCESS(0, "The transaction was applied. Only final in a validated ledger."), - - tecCLAIM(100, "Fee claimed. Sequence used. No action."), - tecPATH_PARTIAL(101, "Path could not send full amount."), - tecUNFUNDED_ADD(102, "Insufficient XRP balance for WalletAdd."), - tecUNFUNDED_OFFER(103, "Insufficient balance to fund created offer."), - tecUNFUNDED_PAYMENT(104, "Insufficient XRP balance to send."), - tecFAILED_PROCESSING(105, "Failed to correctly process transaction."), - tecDIR_FULL(121, "Can not add entry to full directory."), - tecINSUF_RESERVE_LINE(122, "Insufficient reserve to add trust line."), - tecINSUF_RESERVE_OFFER(123, "Insufficient reserve to create offer."), - tecNO_DST(124, "Destination does not exist. Send XRP to create it."), - tecNO_DST_INSUF_XRP(125, "Destination does not exist. Too little XRP sent to create it."), - tecNO_LINE_INSUF_RESERVE(126, "No such line. Too little reserve to create it."), - tecNO_LINE_REDUNDANT(127, "Can't set non-existent line to default."), - tecPATH_DRY(128, "Path could not send partial amount."), - tecUNFUNDED(129, "One of _ADD, _OFFER, or _SEND. Deprecated."), - tecNO_ALTERNATIVE_KEY(130, "The operation would remove the ability to sign transactions with the account."), - tecNO_REGULAR_KEY(131, "Regular key is not set."), - tecOWNERS(132, "Non-zero owner count."), - tecNO_ISSUER(133, "Issuer account does not exist."), - tecNO_AUTH(134, "Not authorized to hold asset."), - tecNO_LINE(135, "No such line."), - tecINSUFF_FEE(136, "Insufficient balance to pay fee."), - tecFROZEN(137, "Asset is frozen."), - tecNO_TARGET(138, "Target account does not exist."), - tecNO_PERMISSION(139, "No permission to perform requested operation."), - tecNO_ENTRY(140, "No matching entry found."), - tecINSUFFICIENT_RESERVE(141, "Insufficient reserve to complete requested operation."), - tecNEED_MASTER_KEY(142, "The operation requires the use of the Master Key."), - tecDST_TAG_NEEDED(143, "A destination tag is required."), - tecINTERNAL(144, "An internal error has occurred during processing."), - tecOVERSIZE(145, "Object exceeded serialization limits."), - tecCRYPTOCONDITION_ERROR(146, "Malformed, invalid, or mismatched conditional or fulfillment."), - tecINVARIANT_FAILED(147, "One or more invariants for the transaction were not satisfied."), - tecEXPIRED(148, "Expiration time is passed."), - tecDUPLICATE(149, "Ledger object already exists."), - tecKILLED(150, "FillOrKill offer killed."), - ; - - - public int asInteger() { - return ord; - } - - @Override - public Type type() { - return Type.UInt8; - } - - private static int ords = 0; - int ord; - public String human; - EngineResult class_ = null; - - EngineResult(Integer i, String s) { - human = s; - if (i == null) { - i = ++Holder.ords; - } else { - Holder.ords = i; - } - ord = i; - } - - EngineResult(String s) { - this(null, s); - } - - private static TreeMap byCode; - - static { - byCode = new TreeMap<>(); - for (EngineResult ter : EngineResult.values()) { - byCode.put(ter.ord, ter); - } - } - - public static EngineResult fromNumber(Number i) { - return byCode.get(i.intValue()); - } - - - /*Serialized Type implementation*/ - @Override - public byte[] toBytes() { - return new byte[]{(byte) ord}; - } - - @Override - public void toBytesSink(BytesSink to) { - to.add((byte) ord); - } - - @Override - public Object toJSON() { - return toString(); - } - - @Override - public String toHex() { - return B16.encode(toBytes()); - } - - public static class Translator extends TypeTranslator { - @Override - public EngineResult fromParser(BinaryParser parser, Integer hint) { - return fromInteger(parser.readOneInt()); - } - - @Override - public EngineResult fromString(String value) { - return EngineResult.valueOf(value); - } - - @Override - public EngineResult fromInteger(int integer) { - return fromNumber(integer); - } - } - - @SuppressWarnings("unused") - public static Translator translate = new Translator(); - - // Result Classes - public static EngineResult resultClass(EngineResult result) { - if (result.ord >= telLOCAL_ERROR.ord && result.ord < temMALFORMED.ord) { - return telLOCAL_ERROR; - } - if (result.ord >= temMALFORMED.ord && result.ord < tefFAILURE.ord) { - return temMALFORMED; - } - if (result.ord >= tefFAILURE.ord && result.ord < terRETRY.ord) { - return tefFAILURE; - } - if (result.ord >= terRETRY.ord && result.ord < tesSUCCESS.ord) { - return terRETRY; - } - if (result.ord >= tesSUCCESS.ord && result.ord < tecCLAIM.ord) { - return tesSUCCESS; - } - return tecCLAIM; - } - - public EngineResult resultClass() { - return class_; - } - - static { - for (EngineResult engineResult : EngineResult.values()) { - engineResult.class_ = resultClass(engineResult); - } - } - -} - - diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/enums/LedgerEntryType.java b/ripple-core/src/main/java/com/ripple/core/serialized/enums/LedgerEntryType.java deleted file mode 100644 index cba003d2d9..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/enums/LedgerEntryType.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.ripple.core.serialized.enums; - -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import com.ripple.encodings.common.B16; - -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - -public enum LedgerEntryType implements SerializedType { - // Invalid (-1), - AccountRoot('a'), - DirectoryNode('d'), - // GeneratorMap ('g'), - RippleState('r'), - Escrow('u'), - // Nickname ('n'), // deprecated - Offer('o'), - // Contract ('c'), - LedgerHashes('h'), - Amendments('f'), - FeeSettings('s'), - Ticket('T'), - SignerList('S'), - PayChannel('x'), - Check('C'), - DepositPreauth('p'); - - final int ord; - - LedgerEntryType(int i) { - ord = i; - } - - static private Map byCode = new TreeMap<>(); - - static { - for (Object a : LedgerEntryType.values()) { - LedgerEntryType f = (LedgerEntryType) a; - byCode.put(f.ord, f); - } - } - - @Override - public Type type() { - return Type.UInt16; - } - - public static LedgerEntryType fromNumber(Number i) { - return byCode.get(i.intValue()); - } - - public Integer asInteger() { - return ord; - } - - // SeralizedType interface - @Override - public byte[] toBytes() { - return new byte[]{(byte) ((ord >>> 8) & 0xFF), (byte) (ord & 0xFF)}; - } - - @Override - public Object toJSON() { - return toString(); - } - - @Override - public String toHex() { - return B16.encode(toBytes()); - } - - @Override - public void toBytesSink(BytesSink to) { - to.add(toBytes()); - } - - public static class Translator extends TypeTranslator { - @Override - public LedgerEntryType fromParser(BinaryParser parser, Integer hint) { - return fromNumber(parser.readOneInt() << 8 | parser.readOneInt()); - } - - @Override - public LedgerEntryType fromInteger(int integer) { - return fromNumber(integer); - } - - @Override - public LedgerEntryType fromString(String value) { - return LedgerEntryType.valueOf(value); - } - } - - @SuppressWarnings("unused") - public static Translator translate = new Translator(); -} diff --git a/ripple-core/src/main/java/com/ripple/core/serialized/enums/TransactionType.java b/ripple-core/src/main/java/com/ripple/core/serialized/enums/TransactionType.java deleted file mode 100644 index 7c98c3da07..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/serialized/enums/TransactionType.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.ripple.core.serialized.enums; - -import com.ripple.core.fields.Type; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.TypeTranslator; -import com.ripple.encodings.common.B16; - -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - -public enum TransactionType implements SerializedType { - // Invalid (-1), - Payment (0), - EscrowCreate (1), // open - EscrowFinish (2), - AccountSet (3), // open, - EscrowCancel (4), - SetRegularKey(5), - // unused1 (6), // open - OfferCreate (7), - OfferCancel (8), - // unused2(9), - TicketCreate(10), - TicketCancel(11), - SignerListSet(12), - PaymentChannelCreate(13), - PaymentChannelFund(14), - PaymentChannelClaim(15), - CheckCreate(16), - CheckCash(17), - CheckCancel(18), - DepositPreauth(19), - TrustSet (20), - EnableAmendment(100), - SetFee(101); - - public int asInteger() { - return ord; - } - - final int ord; - TransactionType(int i) { - ord = i; - } - - @Override - public Type type() { - return Type.UInt16; - } - - static private Map byCode = new TreeMap<>(); - static { - for (Object a : TransactionType.values()) { - TransactionType f = (TransactionType) a; - byCode.put(f.ord, f); - } - } - - static public TransactionType fromNumber(Number i) { - return byCode.get(i.intValue()); - } - - // SeralizedType interface - @Override - public byte[] toBytes() { - // TODO: bytes - return new byte[]{(byte) (ord >> 8), (byte) (ord & 0xFF)}; - } - @Override - public Object toJSON() { - return toString(); - } - @Override - public String toHex() { - return B16.encode(toBytes()); - } - @Override - public void toBytesSink(BytesSink to) { - to.add(toBytes()); - } - public static class Translator extends TypeTranslator { - @Override - public TransactionType fromParser(BinaryParser parser, Integer hint) { - return fromNumber(parser.readOneInt() << 8 | parser.readOneInt()); - } - - @Override - public TransactionType fromInteger(int integer) { - return fromNumber(integer); - } - - @Override - public TransactionType fromString(String value) { - return TransactionType.valueOf(value); - } - } - - @SuppressWarnings("unused") - public static Translator translate = new Translator(); -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/generic/Validation.java b/ripple-core/src/main/java/com/ripple/core/types/known/generic/Validation.java deleted file mode 100644 index f7efd08ed2..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/generic/Validation.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.ripple.core.types.known.generic; - -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.STObject; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; - -public class Validation extends STObject { - public static boolean isValidation(STObject source) { - return source.has(UInt32.LedgerSequence) && - source.has(UInt32.SigningTime) && - source.has(Hash256.LedgerHash) && - source.has(Blob.Signature); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/LedgerEntry.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/LedgerEntry.java deleted file mode 100644 index c5dc3f7f3d..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/LedgerEntry.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.ripple.core.types.known.sle; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.STObject; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.fields.Field; -import com.ripple.core.formats.LEFormat; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.entries.AccountRoot; -import com.ripple.core.types.known.sle.entries.DirectoryNode; -import com.ripple.core.types.known.sle.entries.Offer; -import com.ripple.core.types.known.sle.entries.RippleState; - -import java.util.TreeSet; - -public class LedgerEntry extends STObject { - public LedgerEntry(LedgerEntryType type) { - setFormat(LEFormat.formats.get(type)); - put(Field.LedgerEntryType, type); - } - - public LedgerEntryType ledgerEntryType() {return ledgerEntryType(this);} - public Hash256 index() { return get(Hash256.index); } - public UInt32 flags() {return get(UInt32.Flags);} - public Hash256 ledgerIndex() {return get(Hash256.LedgerIndex);} - - public void flags(UInt32 val) {put(Field.Flags, val);} - public void ledgerIndex(Hash256 val) {put(Field.LedgerIndex, val);} - - public boolean hasLedgerIndex() {return has(Hash256.LedgerIndex);} - - public TreeSet owners() { - TreeSet owners = new TreeSet<>(); - - if (has(Field.LowLimit)) { - owners.add(get(Amount.LowLimit).issuer()); - } - if (has(Field.HighLimit)) { - owners.add(get(Amount.HighLimit).issuer()); - } - if (has(Field.Account)) { - owners.add(get(AccountID.Account)); - } - - return owners; - } - - public void index(Hash256 index) { - put(Hash256.index, index); - } - - public void setDefaults() { - if (flags() == null) { - flags(UInt32.ZERO); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/LedgerHashes.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/LedgerHashes.java deleted file mode 100644 index 9b9086cd72..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/LedgerHashes.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.ripple.core.types.known.sle; - -import com.ripple.core.coretypes.Vector256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.LedgerEntryType; - -public class LedgerHashes extends LedgerEntry { - public LedgerHashes() { - super(LedgerEntryType.LedgerHashes); - } - - public Vector256 hashes() { - return get(Vector256.Hashes); - } - - public void hashes(Vector256 hashes) { - put(Vector256.Hashes, hashes); - } - - public UInt32 lastLedgerSequence() { - return get(UInt32.LastLedgerSequence); - } - - public UInt32 firstLedgerSequence() {return get(UInt32.FirstLedgerSequence);} - - public void firstLedgerSequence(UInt32 val) { put(UInt32.FirstLedgerSequence, val);} - - public void lastLedgerSequence(UInt32 val) { put(UInt32.LastLedgerSequence, val);} - - public boolean hasFirstLedgerSequence() {return has(UInt32.FirstLedgerSequence);} - public boolean hasLastLedgerSequence() {return has(UInt32.LastLedgerSequence);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/ThreadedLedgerEntry.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/ThreadedLedgerEntry.java deleted file mode 100644 index 3739109d97..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/ThreadedLedgerEntry.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ripple.core.types.known.sle; - - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.LedgerEntryType; - -// this class has a PreviousTxnID and PreviousTxnLgrSeq -abstract public class ThreadedLedgerEntry extends LedgerEntry { - public ThreadedLedgerEntry(LedgerEntryType type) { - super(type); - } - public UInt32 previousTxnLgrSeq() {return get(UInt32.PreviousTxnLgrSeq);} - public Hash256 previousTxnID() {return get(Hash256.PreviousTxnID);} - public void previousTxnLgrSeq(UInt32 val) {put(Field.PreviousTxnLgrSeq, val);} - public void previousTxnID(Hash256 val) {put(Field.PreviousTxnID, val);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/AccountRoot.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/AccountRoot.java deleted file mode 100644 index 22f26def99..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/AccountRoot.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.hash.Hash128; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt8; -import com.ripple.core.enums.LedgerFlag; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.ThreadedLedgerEntry; - -public class AccountRoot extends ThreadedLedgerEntry { - public AccountRoot() { - super(LedgerEntryType.AccountRoot); - } - - public UInt32 sequence() {return get(UInt32.Sequence);} - public UInt32 transferRate() {return get(UInt32.TransferRate);} - public UInt32 walletSize() {return get(UInt32.WalletSize);} - public UInt32 ownerCount() {return get(UInt32.OwnerCount);} - public Hash128 emailHash() {return get(Hash128.EmailHash);} - public Hash256 walletLocator() {return get(Hash256.WalletLocator);} - public Amount balance() {return get(Amount.Balance);} - public Blob messageKey() {return get(Blob.MessageKey);} - public Blob domain() {return get(Blob.Domain);} - public AccountID account() {return get(AccountID.Account);} - public AccountID regularKey() {return get(AccountID.RegularKey);} - - public void sequence(UInt32 val) {put(Field.Sequence, val);} - public void transferRate(UInt32 val) {put(Field.TransferRate, val);} - public void walletSize(UInt32 val) {put(Field.WalletSize, val);} - public void ownerCount(UInt32 val) {put(Field.OwnerCount, val);} - public void emailHash(Hash128 val) {put(Field.EmailHash, val);} - public void walletLocator(Hash256 val) {put(Field.WalletLocator, val);} - public void balance(Amount val) {put(Field.Balance, val);} - public void messageKey(Blob val) {put(Field.MessageKey, val);} - public void domain(Blob val) {put(Field.Domain, val);} - public void account(AccountID val) {put(Field.Account, val);} - public void regularKey(AccountID val) {put(Field.RegularKey, val);} - - public boolean requiresAuth() { - return flags().testBit(LedgerFlag.RequireAuth); - } - - public boolean hasAccountTxnID() {return has(Hash256.AccountTxnID);} - public boolean hasDomain() {return has(Blob.Domain);} - public boolean hasEmailHash() {return has(Hash128.EmailHash);} - public boolean hasMessageKey() {return has(Blob.MessageKey);} - public boolean hasRegularKey() {return has(AccountID.RegularKey);} - public boolean hasTickSize() {return has(UInt8.TickSize);} - public boolean hasTransferRate() {return has(UInt32.TransferRate);} - public boolean hasWalletLocator() {return has(Hash256.WalletLocator);} - public boolean hasWalletSize() {return has(UInt32.WalletSize);} - - public Hash256 accountTxnID() {return get(Hash256.AccountTxnID);} - public UInt8 tickSize() {return get(UInt8.TickSize);} - - public void accountTxnID(Hash256 val) { put(Hash256.AccountTxnID, val);} - public void tickSize(UInt8 val) { put(UInt8.TickSize, val);} - - @Override - public void setDefaults() { - super.setDefaults(); - if (ownerCount() == null) { - ownerCount(new UInt32(0)); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Amendments.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Amendments.java deleted file mode 100644 index 8b580af8ef..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Amendments.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.STArray; -import com.ripple.core.coretypes.Vector256; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.LedgerEntry; - -public class Amendments extends LedgerEntry { - public Amendments() { - super(LedgerEntryType.Amendments); - } - - public STArray majorities() {return get(STArray.Majorities);} - public Vector256 amendments() {return get(Vector256.Amendments);} - - public void amendments(Vector256 val) { put(Vector256.Amendments, val);} - public void majorities(STArray val) { put(STArray.Majorities, val);} - - public boolean hasAmendments() {return has(Vector256.Amendments);} - public boolean hasMajorities() {return has(STArray.Majorities);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Check.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Check.java deleted file mode 100644 index 310232b1dd..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Check.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.serialized.enums.LedgerEntryType; - -public class Check extends IndexedLedgerEntry { - public Check() { - super(LedgerEntryType.Check); - } - - public boolean hasSourceTag() {return has(UInt32.SourceTag);} - public boolean hasExpiration() {return has(UInt32.Expiration);} - public boolean hasDestinationTag() {return has(UInt32.DestinationTag);} - public boolean hasInvoiceID() {return has(Hash256.InvoiceID);} - - public UInt32 sourceTag() {return get(UInt32.SourceTag);} - public UInt32 sequence() {return get(UInt32.Sequence);} - public UInt32 expiration() {return get(UInt32.Expiration);} - public UInt32 destinationTag() {return get(UInt32.DestinationTag);} - public UInt64 ownerNode() {return get(UInt64.OwnerNode);} - public UInt64 destinationNode() {return get(UInt64.DestinationNode);} - public Hash256 invoiceID() {return get(Hash256.InvoiceID);} - public Amount sendMax() {return get(Amount.SendMax);} - public AccountID account() {return get(AccountID.Account);} - public AccountID destination() {return get(AccountID.Destination);} - - public void sourceTag(UInt32 val) { put(UInt32.SourceTag, val);} - public void sequence(UInt32 val) { put(UInt32.Sequence, val);} - public void expiration(UInt32 val) { put(UInt32.Expiration, val);} - public void destinationTag(UInt32 val) { put(UInt32.DestinationTag, val);} - public void ownerNode(UInt64 val) { put(UInt64.OwnerNode, val);} - public void destinationNode(UInt64 val) { put(UInt64.DestinationNode, val);} - public void invoiceID(Hash256 val) { put(Hash256.InvoiceID, val);} - public void sendMax(Amount val) { put(Amount.SendMax, val);} - public void destination(AccountID val) { put(AccountID.Destination, val);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/DepositPreauthLe.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/DepositPreauthLe.java deleted file mode 100644 index 7f995ecc1c..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/DepositPreauthLe.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.serialized.enums.LedgerEntryType; - -public class DepositPreauthLe extends IndexedLedgerEntry { - public DepositPreauthLe() { - super(LedgerEntryType.DepositPreauth); - } - public UInt64 ownerNode() {return get(UInt64.OwnerNode);} - public void ownerNode(UInt64 val) { put(UInt64.OwnerNode, val);} - - public AccountID account() {return get(AccountID.Account);} - - public AccountID authorize() {return get(AccountID.Authorize);} - public void authorize(AccountID val) { put(AccountID.Authorize, val);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/DirectoryNode.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/DirectoryNode.java deleted file mode 100644 index f46a7d7973..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/DirectoryNode.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Currency; -import com.ripple.core.coretypes.Vector256; -import com.ripple.core.coretypes.hash.Hash160; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.Index; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.LedgerEntry; - -public class DirectoryNode extends LedgerEntry { - public DirectoryNode() { - super(LedgerEntryType.DirectoryNode); - } - - public UInt64 indexNext() {return get(UInt64.IndexNext);} - public UInt64 indexPrevious() {return get(UInt64.IndexPrevious);} - public UInt64 exchangeRate() {return get(UInt64.ExchangeRate);} - public Hash256 rootIndex() {return get(Hash256.RootIndex);} - public AccountID owner() {return get(AccountID.Owner);} - public Hash160 takerPaysCurrency() {return get(Hash160.TakerPaysCurrency);} - public Hash160 takerPaysIssuer() {return get(Hash160.TakerPaysIssuer);} - public Hash160 takerGetsCurrency() {return get(Hash160.TakerGetsCurrency);} - public Hash160 takerGetsIssuer() {return get(Hash160.TakerGetsIssuer);} - public Vector256 indexes() {return get(Vector256.Indexes);} - public void indexNext(UInt64 val) {put(Field.IndexNext, val);} - public void indexPrevious(UInt64 val) {put(Field.IndexPrevious, val);} - public void exchangeRate(UInt64 val) {put(Field.ExchangeRate, val);} - public void rootIndex(Hash256 val) {put(Field.RootIndex, val);} - public void owner(AccountID val) {put(Field.Owner, val);} - public void takerPaysCurrency(Hash160 val) {put(Field.TakerPaysCurrency, val);} - public void takerPaysIssuer(Hash160 val) {put(Field.TakerPaysIssuer, val);} - public void takerGetsCurrency(Hash160 val) {put(Field.TakerGetsCurrency, val);} - public void takerGetsIssuer(Hash160 val) {put(Field.TakerGetsIssuer, val);} - public void indexes(Vector256 val) {put(Field.Indexes, val);} - - public boolean hasIndexNext() {return has(UInt64.IndexNext);} - public boolean hasIndexPrevious() {return has(UInt64.IndexPrevious);} - public boolean hasExchangeRate() {return has(UInt64.ExchangeRate);} - public boolean hasOwner() {return has(AccountID.Owner);} - public boolean hasTakerPaysCurrency() {return has(Hash160.TakerPaysCurrency);} - public boolean hasTakerPaysIssuer() {return has(Hash160.TakerPaysIssuer);} - public boolean hasTakerGetsCurrency() {return has(Hash160.TakerGetsCurrency);} - public boolean hasTakerGetsIssuer() {return has(Hash160.TakerGetsIssuer);} - - public Hash256 nextIndex() { - return Index.directoryNode(rootIndex(), indexNext()); - } - public Hash256 prevIndex() { - return Index.directoryNode(rootIndex(), indexPrevious()); - } - - public boolean hasPreviousIndex() { - return hasIndexPrevious() && !indexPrevious().isZero(); - } - - public boolean hasNextIndex() { - return hasIndexNext() && !indexNext().isZero(); - } - - public boolean isRootIndex() { - return rootIndex().equals(index()); - } - - public void setExchangeDefaults() { - if (takerGetsCurrency() == null) { - takerGetsCurrency(Currency.XRP); - takerGetsIssuer(AccountID.XRP_ISSUER); - } else if (takerPaysCurrency() == null) { - takerPaysCurrency(Currency.XRP); - takerPaysIssuer(AccountID.XRP_ISSUER); - } - } - - @Override - public void setDefaults() { - super.setDefaults(); - if (exchangeRate() != null) { - setExchangeDefaults(); - } - if (indexes() == null) { - indexes(new Vector256()); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Escrow.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Escrow.java deleted file mode 100644 index ff822c6757..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Escrow.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.Index; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.tx.Transaction; - -import java.util.ArrayList; - -public class Escrow extends IndexedLedgerEntry { - public Escrow() { - super(LedgerEntryType.Escrow); - } - - @Override - public void setDefaults() { - super.setDefaults(); - if (multiParty()) { - if (!has(UInt64.DestinationNode)) { - put(UInt64.DestinationNode, UInt64.ZERO); - } - } - } - - @Override - public ArrayList ownerDirectoryIndexes(Transaction nullableContext) { - ArrayList indexes = super.ownerDirectoryIndexes(nullableContext); - if (multiParty()) { - Hash256 destinationOwnerDir = - Index.ownerDirectory(destination()); - indexes.add(Index.directoryNode( - destinationOwnerDir, destinationNode())); - } - return indexes; - } - - private boolean multiParty() { - return !get(AccountID.Account).equals(destination()); - } - - public AccountID account() {return get(AccountID.Account);} - public AccountID destination() {return get(AccountID.Destination);} - public Amount amount() {return get(Amount.Amount);} - public Blob condition() {return get(Blob.Condition);} - public UInt32 cancelAfter() {return get(UInt32.CancelAfter);} - public UInt32 destinationTag() {return get(UInt32.DestinationTag);} - public UInt32 finishAfter() {return get(UInt32.FinishAfter);} - public UInt32 sourceTag() {return get(UInt32.SourceTag);} - public UInt64 destinationNode() {return get(UInt64.DestinationNode);} - public UInt64 ownerNode() {return get(UInt64.OwnerNode);} - - public void amount(Amount val) { put(Amount.Amount, val);} - public void cancelAfter(UInt32 val) { put(UInt32.CancelAfter, val);} - public void condition(Blob val) { put(Blob.Condition, val);} - public void destination(AccountID val) { put(AccountID.Destination, val);} - public void destinationNode(UInt64 val) { put(UInt64.DestinationNode, val);} - public void destinationTag(UInt32 val) { put(UInt32.DestinationTag, val);} - public void finishAfter(UInt32 val) { put(UInt32.FinishAfter, val);} - public void ownerNode(UInt64 val) { put(UInt64.OwnerNode, val);} - public void sourceTag(UInt32 val) { put(UInt32.SourceTag, val);} - - public boolean hasCancelAfter() {return has(UInt32.CancelAfter);} - public boolean hasCondition() {return has(Blob.Condition);} - public boolean hasDestinationNode() {return has(UInt64.DestinationNode);} - public boolean hasDestinationTag() {return has(UInt32.DestinationTag);} - public boolean hasFinishAfter() {return has(UInt32.FinishAfter);} - public boolean hasSourceTag() {return has(UInt32.SourceTag);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/FeeSettings.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/FeeSettings.java deleted file mode 100644 index ce8fca505d..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/FeeSettings.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.LedgerEntry; - -public class FeeSettings extends LedgerEntry { - public FeeSettings() { - super(LedgerEntryType.Amendments); - } - public UInt32 referenceFeeUnits() {return get(UInt32.ReferenceFeeUnits);} - public UInt32 reserveBase() {return get(UInt32.ReserveBase);} - public UInt32 reserveIncrement() {return get(UInt32.ReserveIncrement);} - public UInt64 baseFee() {return get(UInt64.BaseFee);} - - public void baseFee(UInt64 val) { put(UInt64.BaseFee, val);} - public void referenceFeeUnits(UInt32 val) { put(UInt32.ReferenceFeeUnits, val);} - public void reserveBase(UInt32 val) { put(UInt32.ReserveBase, val);} - public void reserveIncrement(UInt32 val) { put(UInt32.ReserveIncrement, val);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/IHasOwners.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/IHasOwners.java deleted file mode 100644 index 93c15e2f80..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/IHasOwners.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.types.known.tx.Transaction; - -import java.util.ArrayList; - -public interface IHasOwners { - ArrayList ownerDirectoryIndexes(Transaction nullableContext); - AccountID account(Transaction nullableContext); -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/IndexedLedgerEntry.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/IndexedLedgerEntry.java deleted file mode 100644 index 37ff656961..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/IndexedLedgerEntry.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.Index; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.ThreadedLedgerEntry; -import com.ripple.core.types.known.tx.Transaction; - -import java.util.ArrayList; - -public abstract class IndexedLedgerEntry extends ThreadedLedgerEntry implements IHasOwners { - public IndexedLedgerEntry(LedgerEntryType type) { - super(type); - } - - @Override - public void setDefaults() { - super.setDefaults(); - if (!has(Field.OwnerNode)) { - put(UInt64.OwnerNode, UInt64.ZERO); - } - } - - @Override - public AccountID account(Transaction nullableContext) { - AccountID account = account(); - if (account == null) { - if (nullableContext != null) { - account = nullableContext.account(); - } else { - throw new IllegalStateException("Cant determine account for: " + prettyJSON()); - } - } - return account; - } - - @Override - public ArrayList ownerDirectoryIndexes(Transaction nullableContext) { - Hash256 ownerDir = Index.ownerDirectory(account(nullableContext)); - ArrayList indexes = new ArrayList<>(); - indexes.add(Index.directoryNode(ownerDir, ownerNode())); - return indexes; - } - - private UInt64 ownerNode() { - return get(UInt64.OwnerNode); - } - - private AccountID account() { - return get(AccountID.Account); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Offer.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Offer.java deleted file mode 100644 index ceaa7a6732..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Offer.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.*; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.Index; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.ThreadedLedgerEntry; - -import java.math.BigDecimal; -import java.util.Collection; -import java.util.Comparator; -import java.util.Iterator; - -public class Offer extends ThreadedLedgerEntry { - public Offer() { - super(LedgerEntryType.Offer); - } - - /** - * Use the BookDirectory field - * - * @return how much must `pay` to `get` one. - * - */ - public BigDecimal directoryAskQuality() { - return Quality.fromBookDirectory(bookDirectory(), - takerPays().isNative(), - takerGets().isNative()); - } - - /** - * @return how much must `pay` to `get` one. - */ - public BigDecimal askQuality() { - return takerPays().computeQuality(takerGets()); - } - - /** - * @return how much `get` if `pay` one. - */ - public BigDecimal bidQuality() { - return takerGets().computeQuality(takerPays()); - } - - /** - * - * @return One of TakerGets issue, eg 1/USD/BITSTAMP or 1/EUR/SNAPSWAP - */ - public Amount getsOne() { - return takerGets().one(); - } - /** - * - * @return One of TakerPays issue, eg 1/USD/BITSTAMP or 1/EUR/SNAPSWAP - */ - public Amount paysOne() { - return takerPays().one(); - } - - public String getPayCurrencyPair() { - return takerGets().currencyString() + "/" + - takerPays().currencyString(); - } - - // TODO: create an OfferExecution object for this - public STObject executed(STObject finalFields) { - // where `this` is an AffectedNode nodeAsPrevious - STObject executed = new STObject(); - executed.put(Amount.TakerPays, finalFields.get(Amount.TakerPays).subtract(takerPays())); - executed.put(Amount.TakerGets, finalFields.get(Amount.TakerGets).subtract(takerGets())); - return executed; - } - - public Hash256 lineIndex(Amount amt) { - return account().lineIndex(amt.issue()); - } - - public Hash256 fundingSource() { - Amount takerGets = takerGets(); - if (account().equals(takerGets.issuer())) { - return null; - } - else if (takerGets.isNative()) { - return Index.accountRoot(account()); - } else { - return lineIndex(takerGets); - } - } - - public Vector256 lineIndexes() { - Vector256 ret = new Vector256(); - - Amount takerGets = takerGets(); - for (Amount amt : new Amount[]{takerGets, takerPays()}) { - - // Actually want to compare by reference here :) - if (amt == takerGets()) { - // selling own funds - continue; - } - if (!amt.isNative()) { - ret.add(lineIndex(amt)); - } - } - return ret; - } - - public Hash256 bookBase() { - return Index.bookStart(takerPays().issue(), takerGets().issue()); - } - - public boolean belongsToBook(Hash256 bookBase) { - byte[] baseBytes = bookBase.bytes(); - byte[] directoryBytes = bookDirectory().bytes(); - - for (int i = 0; i < 24; i++) { - if (baseBytes[i] != directoryBytes[i]) { - return false; - } - } - return true; - } - - public boolean sellingOwnFunds() { - return account().equals(takerGets().issuer()); - } - - public Amount takerGetsFunded() { - return has(Field.taker_gets_funded) ? get(Amount.taker_gets_funded) : takerGets(); - } - public Amount takerPaysFunded() { - return has(Field.taker_pays_funded) ? get(Amount.taker_pays_funded) : takerPays(); - } - - public static Comparator qualityAscending = Comparator.comparing(Offer::directoryAskQuality); - - public static Iterator iterateCollection(Collection offers) { - final Iterator iterator = offers.iterator(); - - return new Iterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public Offer next() { - return (Offer) iterator.next(); - } - - @Override - public void remove() { - iterator.remove(); - - } - }; - } - - public Hash256 bookNodeDirectoryIndex() { - return Index.directoryNode(bookDirectory(), bookNode()); - } - - public Hash256 ownerNodeDirectoryIndex() { - Hash256 ownerDir = Index.ownerDirectory(account()); - return Index.directoryNode(ownerDir, ownerNode()); - } - - - public UInt32 sequence() {return get(UInt32.Sequence);} - public UInt32 expiration() {return get(UInt32.Expiration);} - public UInt64 bookNode() {return get(UInt64.BookNode);} - public UInt64 ownerNode() {return get(UInt64.OwnerNode);} - public Hash256 bookDirectory() {return get(Hash256.BookDirectory);} - public Amount takerPays() {return get(Amount.TakerPays);} - public Amount takerGets() {return get(Amount.TakerGets);} - public AccountID account() {return get(AccountID.Account);} - public void sequence(UInt32 val) {put(Field.Sequence, val);} - public void expiration(UInt32 val) {put(Field.Expiration, val);} - public void bookNode(UInt64 val) {put(Field.BookNode, val);} - public void ownerNode(UInt64 val) {put(Field.OwnerNode, val);} - public void bookDirectory(Hash256 val) {put(Field.BookDirectory, val);} - public void takerPays(Amount val) {put(Field.TakerPays, val);} - public void takerGets(Amount val) {put(Field.TakerGets, val);} - public void account(AccountID val) {put(Field.Account, val);} - - public boolean hasExpiration() {return has(UInt32.Expiration);} - - public Hash256[] directoryIndexes() { - return new Hash256[]{bookNodeDirectoryIndex(), ownerNodeDirectoryIndex()}; - } - - @Override - public void setDefaults() { - super.setDefaults(); - setOfferDefaults(); - } - - public void setOfferDefaults() { - if (bookNode() == null) { - bookNode(UInt64.ZERO); - } - if (ownerNode() == null) { - ownerNode(UInt64.ZERO); - } - } - - public IssuePair issuePair() { - return new IssuePair(takerPays().issue(), takerGets().issue()); - } - - public Amount payToGet(Amount funded) { - BigDecimal quality = directoryAskQuality(); - // Multiply by one as that will do the rounding operation we want - return paysOne().multiply(funded.multiply(quality)); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/OfferDirectory.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/OfferDirectory.java deleted file mode 100644 index fddb7126fd..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/OfferDirectory.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.Issue; -import com.ripple.core.coretypes.IssuePair; - -public class OfferDirectory extends DirectoryNode { - public IssuePair issuePair() { - return new IssuePair(takerPaysIssue(), takerGetsIssue()); - } - - public Issue takerGetsIssue() { - // TODO: remove wrapper - return Issue.from160s(takerGetsCurrency(), takerGetsIssuer()); - } - public Issue takerPaysIssue() { - // TODO: remove wrapper - return Issue.from160s(takerPaysCurrency(), takerPaysIssuer()); - } - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/OwnerDirectory.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/OwnerDirectory.java deleted file mode 100644 index b6f784e4b8..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/OwnerDirectory.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -public class OwnerDirectory extends DirectoryNode { } diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/PayChannel.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/PayChannel.java deleted file mode 100644 index 97a2786b2a..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/PayChannel.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.serialized.enums.LedgerEntryType; - -public class PayChannel extends IndexedLedgerEntry { - public PayChannel() { - super(LedgerEntryType.PayChannel); - } - - public AccountID account() {return get(AccountID.Account);} - public AccountID destination() {return get(AccountID.Destination);} - public Amount amount() {return get(Amount.Amount);} - public Amount balance() {return get(Amount.Balance);} - public Blob publicKey() {return get(Blob.PublicKey);} - public UInt32 cancelAfter() {return get(UInt32.CancelAfter);} - public UInt32 destinationTag() {return get(UInt32.DestinationTag);} - public UInt32 expiration() {return get(UInt32.Expiration);} - public UInt32 settleDelay() {return get(UInt32.SettleDelay);} - public UInt32 sourceTag() {return get(UInt32.SourceTag);} - public UInt64 ownerNode() {return get(UInt64.OwnerNode);} - - public void amount(Amount val) { put(Amount.Amount, val);} - public void balance(Amount val) { put(Amount.Balance, val);} - public void cancelAfter(UInt32 val) { put(UInt32.CancelAfter, val);} - public void destination(AccountID val) { put(AccountID.Destination, val);} - public void destinationTag(UInt32 val) { put(UInt32.DestinationTag, val);} - public void expiration(UInt32 val) { put(UInt32.Expiration, val);} - public void ownerNode(UInt64 val) { put(UInt64.OwnerNode, val);} - public void publicKey(Blob val) { put(Blob.PublicKey, val);} - public void settleDelay(UInt32 val) { put(UInt32.SettleDelay, val);} - public void sourceTag(UInt32 val) { put(UInt32.SourceTag, val);} - - public boolean hasCancelAfter() {return has(UInt32.CancelAfter);} - public boolean hasDestinationTag() {return has(UInt32.DestinationTag);} - public boolean hasExpiration() {return has(UInt32.Expiration);} - public boolean hasSourceTag() {return has(UInt32.SourceTag);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/RippleState.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/RippleState.java deleted file mode 100644 index 02f9f2ad2e..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/RippleState.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.Currency; -import com.ripple.core.coretypes.Issue; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.Index; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.enums.LedgerFlag; -import com.ripple.core.fields.AmountField; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.ThreadedLedgerEntry; - -import java.util.Arrays; -import java.util.List; - -public class RippleState extends ThreadedLedgerEntry { - /** - The RippleState is a ledger entry which roughly speaking defines the balance and - trust limits between two accounts. - - Like all current ledger entries, it has a canonical form for hashing that - doesn't necessarily communicate the information in a way that is clear or - obvious to a human. The current json format for some ledger entries is very - close to the hashing format, and isn't any better. - - The two accounts on the link are categorized into a low account, and a high - account by comparing the 160bits of their account id as a big endian unsigned - integer. - - There is one and only one `Balance` stored, using an amount struct with a - neutral `issuer`. ( A uint160 with the numerical value of `1` was chosen as a - placeholder, as the canonical way to represent a null account ) - - The Balance can be negative, zero, or positive and is in terms of the Low - account, such that when it's positive, it defines how much credit the High - account has issued. - - Between any account, there can be two types of balance changes, issuance and - redemption. A redemption is the transferal of previously issued IOUs back to the - owner. - - This implies that for any account, that can they can make a transferal using - funds from two distinct balances. After making this distinction it becomes - nonsensical to say that an account holds negative IOUs from the opposite line. - - Thus, the one Balance in a ripple state actually implies 4 distinct balances. - - ``` - { - ... - "Balance" : { - "currency" : "USD", - "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji", - "value" : "100" - }, - "HighLimit" : { - "currency" : "USD", - "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - "value" : "500" - }, - "LowLimit" : { - "currency" : "USD", - "issuer" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", - "value" : "500" - }, - } - ``` - - lowAccount = LowLimit.issuer - hiAccount = HighLimit.issuer - - Balances: - lowAccount has 100/USD/highAccount - lowAccount has 0/USD/lowAccount on the line - - highAccount has 0/USD/lowAccount - highAccount has -100/USD/highAccount on the line - - - * */ - - public RippleState() { - super(LedgerEntryType.RippleState); - } - - public UInt32 highQualityIn() {return get(UInt32.HighQualityIn);} - public UInt32 highQualityOut() {return get(UInt32.HighQualityOut);} - public UInt32 lowQualityIn() {return get(UInt32.LowQualityIn);} - public UInt32 lowQualityOut() {return get(UInt32.LowQualityOut);} - public UInt64 lowNode() {return get(UInt64.LowNode);} - public UInt64 highNode() {return get(UInt64.HighNode);} - public Amount balance() {return get(Amount.Balance);} - public Amount lowLimit() {return get(Amount.LowLimit);} - public Amount highLimit() {return get(Amount.HighLimit);} - public void highQualityIn(UInt32 val) {put(Field.HighQualityIn, val);} - public void highQualityOut(UInt32 val) {put(Field.HighQualityOut, val);} - public void lowQualityIn(UInt32 val) {put(Field.LowQualityIn, val);} - public void lowQualityOut(UInt32 val) {put(Field.LowQualityOut, val);} - public void lowNode(UInt64 val) {put(Field.LowNode, val);} - public void highNode(UInt64 val) {put(Field.HighNode, val);} - public void balance(Amount val) {put(Field.Balance, val);} - public void lowLimit(Amount val) {put(Field.LowLimit, val);} - public void highLimit(Amount val) {put(Field.HighLimit, val);} - - public boolean hasHighQualityIn() {return has(UInt32.HighQualityIn);} - public boolean hasHighQualityOut() {return has(UInt32.HighQualityOut);} - public boolean hasLowQualityIn() {return has(UInt32.LowQualityIn);} - public boolean hasLowQualityOut() {return has(UInt32.LowQualityOut);} - public boolean hasLowNode() {return has(UInt64.LowNode);} - public boolean hasHighNode() {return has(UInt64.HighNode);} - - public AccountID lowAccount() { - return lowLimit().issuer(); - } - - public AccountID highAccount() { - return highLimit().issuer(); - } - - public List sortedAccounts() { - return Arrays.asList(lowAccount(), highAccount()); - } - - public AmountField limitFieldFor(AccountID source) { - if (lowAccount().equals(source)) { - return Amount.LowLimit; - } - if (highAccount().equals(source)) { - return Amount.HighLimit; - } else { - return null; - } - } - - public boolean isFor(AccountID source) { - return lowAccount().equals(source) || highAccount().equals(source); - } - - public boolean isFor(Issue issue) { - return isFor(issue.issuer()) && balance().currency().equals(issue.currency()); - } - - // TODO, can optimize this - public boolean isFor(AccountID s1, AccountID s2, Currency currency) { - return currency.equals(balance().currency()) && isFor(s1) && isFor(s2); - } - - public Currency currency() { - return balance().currency(); - } - - private Amount issuedBy(boolean hi) { - Amount balance; - - if (hi) { - balance = balance().newIssuer(highAccount()); - } else { - balance = balance().negate().newIssuer(lowAccount()); - } - - if (!balance.isPositive()) { - balance = balance.issue().amount(0); - } - return balance; - } - - public Amount issuedByHigh() { - return issuedBy(true); - } - public Amount issuedByLow() { - return issuedBy(false); - } - - public Amount issuedTo(AccountID accountID) { - return issuedBy(isLowAccount(accountID)); - } - - @Deprecated() // "not deprecated but needs fixing" - public boolean authorizedBy(AccountID account) { - UInt32 flags = flags(); - return flags == null || flags.testBit(isHighAccount(account) ? LedgerFlag.HighAuth : LedgerFlag.LowAuth); - } - - private boolean isBitSet(int flags, int flag) { - return (flags & flag) != 0; - } - - private boolean isHighAccount(AccountID account) { - return highAccount().equals(account); - } - private boolean isLowAccount(AccountID account) { - return lowAccount().equals(account); - } - - - public Hash256 lowNodeOwnerDirectory() { - Hash256 ownerDir = Index.ownerDirectory(lowAccount()); - return Index.directoryNode(ownerDir, lowNode()); - } - public Hash256 highNodeOwnerDirectory() { - Hash256 ownerDir = Index.ownerDirectory(highAccount()); - return Index.directoryNode(ownerDir, highNode()); - } - - public Hash256[] directoryIndexes() { - return new Hash256[]{lowNodeOwnerDirectory(), highNodeOwnerDirectory()}; - } - - @Override - public void setDefaults() { - super.setDefaults(); - - if (lowNode() == null) { - lowNode(UInt64.ZERO); - } - if (highNode() == null) { - highNode(UInt64.ZERO); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/SignerList.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/SignerList.java deleted file mode 100644 index 68af847605..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/SignerList.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.STArray; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.LedgerEntryType; - -public class SignerList extends IndexedLedgerEntry { - public SignerList() { - super(LedgerEntryType.SignerList); - } - - @Override - public void setDefaults() { - super.setDefaults(); - if (!has(Field.SignerListID)) { - put(UInt32.SignerListID, UInt32.ZERO); - } - } - - public STArray signerEntries() {return get(STArray.SignerEntries);} - public UInt32 signerListID() {return get(UInt32.SignerListID);} - public UInt32 signerQuorum() {return get(UInt32.SignerQuorum);} - public UInt64 ownerNode() {return get(UInt64.OwnerNode);} - - public void ownerNode(UInt64 val) { put(UInt64.OwnerNode, val);} - public void signerEntries(STArray val) { put(STArray.SignerEntries, val);} - public void signerListID(UInt32 val) { put(UInt32.SignerListID, val);} - public void signerQuorum(UInt32 val) { put(UInt32.SignerQuorum, val);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Ticket.java b/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Ticket.java deleted file mode 100644 index 5d48e3f462..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/sle/entries/Ticket.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ripple.core.types.known.sle.entries; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.serialized.enums.LedgerEntryType; - -public class Ticket extends IndexedLedgerEntry { - public Ticket() { - super(LedgerEntryType.Ticket); - } - - public AccountID account() {return get(AccountID.Account);} - public AccountID target() {return get(AccountID.Target);} - public UInt32 expiration() {return get(UInt32.Expiration);} - public UInt32 sequence() {return get(UInt32.Sequence);} - public UInt64 ownerNode() {return get(UInt64.OwnerNode);} - - public void expiration(UInt32 val) { put(UInt32.Expiration, val);} - public void ownerNode(UInt64 val) { put(UInt64.OwnerNode, val);} - public void sequence(UInt32 val) { put(UInt32.Sequence, val);} - public void target(AccountID val) { put(AccountID.Target, val);} - - public boolean hasExpiration() {return has(UInt32.Expiration);} - public boolean hasTarget() {return has(AccountID.Target);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/Transaction.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/Transaction.java deleted file mode 100644 index 8f174968aa..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/Transaction.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.ripple.core.types.known.tx; - -import com.ripple.core.coretypes.*; -import com.ripple.core.coretypes.hash.HalfSha512; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.uint.UInt16; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.enums.TransactionFlag; -import com.ripple.core.fields.Field; -import com.ripple.core.formats.TxFormat; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.signed.SignedTransaction; -import com.ripple.crypto.keys.IVerifyingKey; -import com.ripple.utils.HashUtils; - -public class Transaction extends STObject { - public static final boolean CANONICAL_FLAG_DEPLOYED = true; - public static final UInt32 CANONICAL_SIGNATURE = new UInt32(TransactionFlag.FullyCanonicalSig); - - public Transaction(TransactionType type) { - setFormat(TxFormat.formats.get(type)); - put(Field.TransactionType, type); - } - - public SignedTransaction prepare(byte[] pubKeyBytes) { - SignedTransaction tx = SignedTransaction.fromTx(this); - tx.prepare(pubKeyBytes); - return tx; - } - - public TransactionType transactionType() { - return transactionType(this); - } - - public Hash256 signingHash() { - return signingHash(HashPrefix.txSign); - } - - public byte[] signingData() { - return signingData(HashPrefix.txSign); - } - - public boolean verifySignature(AccountID key) { - Blob pubKey = signingPubKey(); - return IVerifyingKey.from(pubKey.toBytes()) - .verify(signingData(), - txnSignature().toBytes()) && - signingKey().equals(key); - } - - public boolean verifyMasterKeySignature() { - return verifySignature(account()); - } - - public void setCanonicalSignatureFlag() { - UInt32 flags = get(UInt32.Flags); - if (flags == null) { - flags = CANONICAL_SIGNATURE; - } else { - flags = flags.or(CANONICAL_SIGNATURE); - } - put(UInt32.Flags, flags); - } - - public UInt32 flags() {return get(UInt32.Flags);} - public UInt32 sourceTag() {return get(UInt32.SourceTag);} - public UInt32 sequence() {return get(UInt32.Sequence);} - public UInt32 lastLedgerSequence() {return get(UInt32.LastLedgerSequence);} - public UInt32 operationLimit() {return get(UInt32.OperationLimit);} - public Hash256 previousTxnID() {return get(Hash256.PreviousTxnID);} - public Hash256 accountTxnID() {return get(Hash256.AccountTxnID);} - public Amount fee() {return get(Amount.Fee);} - public Blob signingPubKey() {return get(Blob.SigningPubKey);} - public Blob txnSignature() {return get(Blob.TxnSignature);} - public AccountID account() {return get(AccountID.Account);} - - public void transactionType(UInt16 val) {put(Field.TransactionType, val);} - public void flags(UInt32 val) {put(Field.Flags, val);} - public void sourceTag(UInt32 val) {put(Field.SourceTag, val);} - public void sequence(UInt32 val) {put(Field.Sequence, val);} - public void lastLedgerSequence(UInt32 val) {put(Field.LastLedgerSequence, val);} - public void operationLimit(UInt32 val) {put(Field.OperationLimit, val);} - public void previousTxnID(Hash256 val) {put(Field.PreviousTxnID, val);} - public void accountTxnID(Hash256 val) {put(Field.AccountTxnID, val);} - public void fee(Amount val) {put(Field.Fee, val);} - public void signingPubKey(Blob val) {put(Field.SigningPubKey, val);} - public void txnSignature(Blob val) {put(Field.TxnSignature, val);} - public void account(AccountID val) {put(Field.Account, val);} - - public boolean hasFlags() {return has(UInt32.Flags);} - public boolean hasSourceTag() {return has(UInt32.SourceTag);} - public boolean hasLastLedgerSequence() {return has(UInt32.LastLedgerSequence);} - public boolean hasOperationLimit() {return has(UInt32.OperationLimit);} - public boolean hasPreviousTxnID() {return has(Hash256.PreviousTxnID);} - public boolean hasAccountTxnID() {return has(Hash256.AccountTxnID);} - public boolean hasTxnSignature() {return has(Blob.TxnSignature);} - public boolean hasSigners() {return has(STArray.Signers);} - public boolean hasMemos() {return has(STArray.Memos);} - - public Hash256 hash() { - return get(Hash256.hash); - } - - public Hash256 createHash() { - HalfSha512 id = HalfSha512.prefixed256(HashPrefix.transactionID); - toBytesSink(id); - return id.finish(); - } - - public AccountID signingKey() { - // May be a regular Key - byte[] pubKey = HashUtils.SHA256_RIPEMD160(signingPubKey().toBytes()); - return AccountID.fromBytes(pubKey); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/AffectedNode.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/AffectedNode.java deleted file mode 100644 index 9c399b73a0..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/AffectedNode.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.ripple.core.types.known.tx.result; - -import com.ripple.core.coretypes.STObject; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt16; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.SerializedType; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.LedgerEntry; - -// TODO: fix up this nonsense -public class AffectedNode extends STObject { - public final Field field; - public final STObject nested; - - public AffectedNode(STObject source) { - fields = source.getFields(); - field = getField(); - nested = nestedObject(); - } - - public boolean isOffer() { - return ledgerEntryType() == LedgerEntryType.Offer; - } - - public boolean isAccountRoot() { - return ledgerEntryType() == LedgerEntryType.AccountRoot; - } - - public boolean isRippleState() { - return ledgerEntryType() == LedgerEntryType.RippleState; - } - - public boolean isDirectoryNode() { - return ledgerEntryType() == LedgerEntryType.DirectoryNode; - } - - public boolean wasPreviousNode() { - return isDeletedNode() || isModifiedNode(); - } - - public boolean isFinalNode() { - return true; - } - - public boolean isCreatedNode() { - return field == Field.CreatedNode; - } - - public boolean isDeletedNode() { - return field == Field.DeletedNode; - } - - public boolean isModifiedNode() { - return field == Field.ModifiedNode; - } - - public Field getField() { -// return iterator().next(); - return fields.firstKey(); - } - - public Hash256 ledgerIndex() { - return nested.get(Hash256.LedgerIndex); - } - - public LedgerEntryType ledgerEntryType() { - return ledgerEntryType(nested); - } - - private STObject nestedObject() { - return (STObject) get(getField()); - } - - /** - * @return - LedgerEntry before the transaction (or after in the case of - * a CreatedNode) - */ - public LedgerEntry nodeAsPrevious() { - return (LedgerEntry) rebuildFromMeta(true); - } - - public LedgerEntry nodeAsFinal() { - return (LedgerEntry) rebuildFromMeta(false); - } - - private STObject rebuildFromMeta(boolean asPrevious) { - boolean created = isCreatedNode(); - STObject mixed = new STObject(); - - // The first object has only a single key - Field wrapperField = created ? Field.CreatedNode : - isDeletedNode() ? Field.DeletedNode : - Field.ModifiedNode; - - STObject wrapped = (STObject) get(wrapperField); - - Field finalFields = created ? Field.NewFields : - Field.FinalFields; - - // You may get some AccountRoot objects like this - if (!wrapped.has(finalFields)) { - STObject source = new STObject(wrapped.getFields()); - source.put(Hash256.index, wrapped.get(Hash256.LedgerIndex)); - source.remove(Field.LedgerIndex); - return STObject.formatted(source); - } - - // Get all the final fields - STObject finals = (STObject) wrapped.get(finalFields); - for (Field field : finals) { - mixed.put(field, finals.get(field)); - } - - // Then layer over the previous fields if desired as previous - // DirectoryNode LedgerEntryType won't have `PreviousFields` - if (asPrevious && wrapped.has(Field.PreviousFields)) { - STObject previous = wrapped.get(STObject.PreviousFields); - for (Field field : previous) { - mixed.put(field, previous.get(field)); - } - } - - // Keep the inner most fields - for (Field field : wrapped) { - switch (field) { - case NewFields: - case PreviousFields: - case FinalFields: - continue; - default: - SerializedType value = wrapped.get(field); - if (field == Field.LedgerIndex) { - field = Field.index; - } - mixed.put(field, value); - - } - } - return STObject.formatted(mixed); - } - - public static boolean isAffectedNode(STObject source) { - return (source.size() == 1 && ( - source.has(DeletedNode) || - source.has(CreatedNode) || - source.has(ModifiedNode))); - } - - public boolean removedField(Field field) { - return nested.has(Field.PreviousFields) && - nested.get(STObject.PreviousFields).has(field) && - nested.has(Field.FinalFields) && - !nested.get(STObject.FinalFields).has(field); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/TransactionMeta.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/TransactionMeta.java deleted file mode 100644 index c25183436a..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/TransactionMeta.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.ripple.core.types.known.tx.result; - -import com.ripple.core.coretypes.STArray; -import com.ripple.core.coretypes.STObject; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt8; -import com.ripple.core.serialized.enums.EngineResult; -import com.ripple.core.types.known.sle.LedgerEntry; - -import java.util.Iterator; - -public class TransactionMeta extends STObject { - public static boolean isTransactionMeta(STObject source) { - return source.has(UInt8.TransactionResult) && - source.has(STArray.AffectedNodes); - } - - public EngineResult engineResult() { - return engineResult(this); - } - - public Iterable affectedNodes() { - STArray nodes = get(STArray.AffectedNodes); - final Iterator iterator = nodes.iterator(); - return () -> iterateAffectedNodes(iterator); - } - - private static Iterator iterateAffectedNodes(final Iterator iterator) { - return new Iterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public AffectedNode next() { - return (AffectedNode) iterator.next(); - } - - @Override - public void remove() { - iterator.remove(); - } - }; - } - - public UInt32 transactionIndex() { - return get(UInt32.TransactionIndex); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/TransactionResult.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/TransactionResult.java deleted file mode 100644 index 2e4467bf14..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/result/TransactionResult.java +++ /dev/null @@ -1,299 +0,0 @@ -package com.ripple.core.types.known.tx.result; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.STArray; -import com.ripple.core.coretypes.STObject; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.Index; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.EngineResult; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.sle.entries.AccountRoot; -import com.ripple.core.types.known.tx.Transaction; -import com.ripple.encodings.common.B16; -import org.json.JSONObject; - -import java.util.Map; -import java.util.TreeMap; - -public class TransactionResult implements Comparable { - // The json formatting of transaction results is a MESS - public enum Source { - request_tx_result, - request_account_tx, - request_account_tx_binary, - request_tx_binary, - ledger_transactions_expanded_with_ledger_index_injected, - transaction_subscription_notification - } - - public EngineResult engineResult; - public UInt32 ledgerIndex; - public Hash256 hash; - - // TODO: in practice this class is GENERALLY only for validated results. - // NOT strictly true as you can get results from closed but not validated - // ledgers! Though you would want to beyond testing ... - public boolean validated; - - public TransactionResult(long ledgerIndex, Hash256 hash, Transaction txn, TransactionMeta meta) { - this.ledgerIndex = new UInt32(ledgerIndex); - this.hash = hash; - this.txn = txn; - this.meta = meta; - this.engineResult = meta.engineResult(); - this.validated = true; - } - - public Transaction txn; - public TransactionMeta meta; - - public TransactionType transactionType() { - return txn.transactionType(); - } - - public AccountID createdAccount() { - AccountID destination = null; - Hash256 destinationIndex = null; - - if (transactionType() == TransactionType.Payment && meta.has(Field.AffectedNodes)) { - STArray affected = meta.get(STArray.AffectedNodes); - for (STObject node : affected) { - if (node.has(STObject.CreatedNode)) { - STObject created = node.get(STObject.CreatedNode); - if (STObject.ledgerEntryType(created) == LedgerEntryType.AccountRoot) { - if (destination == null) { - destination = txn.get(AccountID.Destination); - destinationIndex = Index.accountRoot(destination); - } - if (destinationIndex.equals(created.get(Hash256.LedgerIndex))) { - return destination; - } - } - } - } - } - return null; - } - - public Map modifiedRoots() { - TreeMap accounts = null; - - if (meta.has(Field.AffectedNodes)) { - accounts = new TreeMap<>(); - for (AffectedNode fields : meta.affectedNodes()) { - if (fields.isModifiedNode() && fields.isAccountRoot()) { - AccountRoot root = (AccountRoot) fields.nodeAsFinal(); - //noinspection StatementWithEmptyBody - if (root.account() != null) { - accounts.put(root.account(), root); - } else { - // TODO: Remember why these modified nodes have no - // FinalFields or NewFields - /* - {"ModifiedNode": { - "LedgerIndex": "2C6F7594FB7471F4983C2BC691AAC2F25F8DB88D455985B4181E053D7AB23006", - "PreviousTxnLgrSeq": 35561097, - "LedgerEntryType": "AccountRoot", - "index": "2C6F7594FB7471F4983C2BC691AAC2F25F8DB88D455985B4181E053D7AB23006", - "PreviousTxnID": "67EE84E892FBEDF5FD52D511FF1E833870A2B8104CA5FF9BA89867A528A5D3ED" - }} - * */ - } - } - } - } - return accounts; - } - - public AccountID initiatingAccount() { - return txn.get(AccountID.Account); - } - - public int compareTo(TransactionResult o2) { - TransactionResult o1 = this; - int i = o1.ledgerIndex.compareTo(o2.ledgerIndex); - if (i != 0) { - return i; - } else { - return o1.meta.transactionIndex() - .compareTo(o2.meta.transactionIndex()); - } - } - - public static TransactionResult fromJSON(ObjectNode json) { - // TODO: obviously ... - return fromJSON(new JSONObject(json.toString())); - } - - public static TransactionResult fromJSON(JSONObject json) { - boolean binary; - - String metaKey = json.has("meta") ? "meta" : "metaData"; - - String txKey = json.has("transaction") ? "transaction" : - json.has("tx") ? "tx" : - json.has("tx_blob") ? "tx_blob" : null; - - if (txKey == null && !json.has("TransactionType")) { - throw new IllegalArgumentException("This json isn't a transaction " + json); - } - - binary = txKey != null && json.get(txKey) instanceof String; - - Transaction txn; - if (txKey == null) { - // This should parse the `hash` field - txn = (Transaction) STObject.fromJSONObject(json); - } else { - txn = (Transaction) parseObject(json, txKey, binary); - if (json.has("hash")) { - txn.put(Hash256.hash, Hash256.fromHex(json.getString("hash"))); - } else if (binary) { - byte[] decode = B16.decode(json.getString(txKey)); - txn.put(Hash256.hash, Index.transactionID(decode)); - } - } - - TransactionMeta meta = (TransactionMeta) parseObject(json, metaKey, binary); - long ledger_index = json.optLong("ledger_index", 0); - if (ledger_index == 0 && !binary) { - ledger_index = json.getJSONObject(txKey).getLong("ledger_index"); - } - - TransactionResult tr = new TransactionResult(ledger_index, txn.get(Hash256.hash), txn, meta); -// if (json.has("ledger_hash")) { -// tr.ledgerHash = Hash256.fromHex(json.getString("ledger_hash")); -// } - return tr; - } - - private static STObject parseObject(JSONObject json, String key, boolean binary) { - if (binary) { - return STObject.fromHex(json.getString(key)); - } else { - JSONObject tx_json = json.getJSONObject(key); - return STObject.fromJSONObject(tx_json); - } - } - - public TransactionResult(JSONObject json, Source resultMessageSource) { - if (resultMessageSource == Source.transaction_subscription_notification) { - - engineResult = EngineResult.valueOf(json.getString("engine_result")); - validated = json.getBoolean("validated"); -// ledgerHash = Hash256.fromHex(json.getString("ledger_hash")); - ledgerIndex = new UInt32(json.getLong("ledger_index")); - - if (json.has("transaction")) { - txn = (Transaction) STObject.fromJSONObject(json.getJSONObject("transaction")); - hash = txn.get(Hash256.hash); - } - - if (json.has("meta")) { - meta = (TransactionMeta) STObject.fromJSONObject(json.getJSONObject("meta")); - } - } - else if (resultMessageSource == Source.ledger_transactions_expanded_with_ledger_index_injected) { - validated = true; - meta = (TransactionMeta) STObject.fromJSONObject(json.getJSONObject("metaData")); - txn = (Transaction) STObject.fromJSONObject(json); - hash = txn.get(Hash256.hash); - engineResult = meta.engineResult(); - ledgerIndex = new UInt32(json.getLong("ledger_index")); - - } else if (resultMessageSource == Source.request_tx_result) { - validated = json.optBoolean("validated", false); - if (validated && !json.has("meta")) { - throw new IllegalStateException("It's validated, why doesn't it have meta??"); - } - if (validated) { - meta = (TransactionMeta) STObject.fromJSONObject(json.getJSONObject("meta")); - engineResult = meta.engineResult(); - txn = (Transaction) STObject.fromJSONObject(json); - hash = txn.get(Hash256.hash); - ledgerIndex = new UInt32(json.getLong("ledger_index")); - - } - } else if (resultMessageSource == Source.request_account_tx) { - validated = json.optBoolean("validated", false); - if (validated && !json.has("meta")) { - throw new IllegalStateException("It's validated, why doesn't it have meta??"); - } - if (validated) { - JSONObject tx = json.getJSONObject("tx"); - meta = (TransactionMeta) STObject.fromJSONObject(json.getJSONObject("meta")); - engineResult = meta.engineResult(); - this.txn = (Transaction) STObject.fromJSONObject(tx); - hash = this.txn.get(Hash256.hash); - ledgerIndex = new UInt32(tx.getLong("ledger_index")); - } - } else if (resultMessageSource == Source.request_account_tx_binary || resultMessageSource == Source.request_tx_binary) { - validated = json.optBoolean("validated", false); - if (validated && !json.has("meta")) { - throw new IllegalStateException("It's validated, why doesn't it have meta??"); - } - if (validated) { - /* - { - "ledger_index": 3378767, - "meta": "201 ...", - "tx_blob": "120 ...", - "validated": true - }, - */ - boolean account_tx = resultMessageSource == Source.request_account_tx_binary; - - String tx = json.getString(account_tx ? "tx_blob" : "tx"); - byte[] decodedTx = B16.decode(tx); - meta = (TransactionMeta) STObject.fromHex(json.getString("meta")); - this.txn = (Transaction) STObject.fromBytes(decodedTx); - - if (account_tx) { - hash = Index.transactionID(decodedTx); - } else { - hash = Hash256.fromHex(json.getString("hash")); - } - this.txn.put(Field.hash, hash); - - engineResult = meta.engineResult(); - ledgerIndex = new UInt32(json.getLong("ledger_index")); - } - } - } - - @Override - public String toString() { - JSONObject object = toJSON(); - return object.toString(); - } - - public JSONObject toJSON() { - JSONObject o = new JSONObject(); - o.put("tx", txn.toJSON()); - o.put("meta", meta.toJSON()); - o.put("ledger_index", ledgerIndex); - o.put("hash", hash.toHex()); - return o; - } - - public TransactionResult copy() { - TransactionMeta metaCopy = (TransactionMeta) STObject.fromBytes(meta.toBytes()); - Transaction txnCopy = (Transaction) STObject.fromBytes(txn.toBytes()); - return new TransactionResult(ledgerIndex.longValue(), hash, txnCopy, metaCopy); - } - - public JSONObject toJSONBinary() { - JSONObject o = new JSONObject(); - - o.put("hash", hash.toHex()); - o.put("meta", meta.toHex()); - o.put("tx", txn.toHex()); - o.put("ledger_index", ledgerIndex); - - return o; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/AccountSet.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/AccountSet.java deleted file mode 100644 index 0e8a1c9672..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/AccountSet.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.hash.Hash128; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt8; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class AccountSet extends Transaction{ - public AccountSet() { - super(TransactionType.AccountSet); - } - public UInt32 transferRate() {return get(UInt32.TransferRate);} - public UInt32 walletSize() {return get(UInt32.WalletSize);} - public UInt32 setFlag() {return get(UInt32.SetFlag);} - public UInt32 clearFlag() {return get(UInt32.ClearFlag);} - public Hash128 emailHash() {return get(Hash128.EmailHash);} - public Hash256 walletLocator() {return get(Hash256.WalletLocator);} - public Blob messageKey() {return get(Blob.MessageKey);} - public Blob domain() {return get(Blob.Domain);} - public UInt8 tickSize() {return get(UInt8.TickSize);} - - public void transferRate(UInt32 val) {put(Field.TransferRate, val);} - public void walletSize(UInt32 val) {put(Field.WalletSize, val);} - public void setFlag(UInt32 val) {put(Field.SetFlag, val);} - public void clearFlag(UInt32 val) {put(Field.ClearFlag, val);} - public void emailHash(Hash128 val) {put(Field.EmailHash, val);} - public void walletLocator(Hash256 val) {put(Field.WalletLocator, val);} - public void messageKey(Blob val) {put(Field.MessageKey, val);} - public void domain(Blob val) {put(Field.Domain, val);} - public void tickSize(UInt8 val) { put(UInt8.TickSize, val);} - - public boolean hasTransferRate() {return has(UInt32.TransferRate);} - public boolean hasWalletSize() {return has(UInt32.WalletSize);} - public boolean hasSetFlag() {return has(UInt32.SetFlag);} - public boolean hasClearFlag() {return has(UInt32.ClearFlag);} - public boolean hasEmailHash() {return has(Hash128.EmailHash);} - public boolean hasWalletLocator() {return has(Hash256.WalletLocator);} - public boolean hasMessageKey() {return has(Blob.MessageKey);} - public boolean hasDomain() {return has(Blob.Domain);} - public boolean hasTickSize() {return has(UInt8.TickSize);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCancel.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCancel.java deleted file mode 100644 index f5b961f514..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCancel.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class CheckCancel extends Transaction { - public CheckCancel() { - super(TransactionType.CheckCancel); - } - - public Hash256 checkID() {return get(Hash256.CheckID);} - public void checkID(Hash256 val) { put(Hash256.CheckID, val);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCash.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCash.java deleted file mode 100644 index 8eba2458e9..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCash.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class CheckCash extends Transaction { - public CheckCash() { - super(TransactionType.CheckCash); - } - - public boolean hasAmount() {return has(Amount.Amount);} - public boolean hasDeliverMin() {return has(Amount.DeliverMin);} - - public Hash256 checkID() {return get(Hash256.CheckID);} - public Amount amount() {return get(Amount.Amount);} - public Amount deliverMin() {return get(Amount.DeliverMin);} - - public void checkID(Hash256 val) { put(Hash256.CheckID, val);} - public void amount(Amount val) { put(Amount.Amount, val);} - public void deliverMin(Amount val) { put(Amount.DeliverMin, val);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCreate.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCreate.java deleted file mode 100644 index b8e75a9b0f..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/CheckCreate.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class CheckCreate extends Transaction { - public CheckCreate() { - super(TransactionType.CheckCreate); - } - - public boolean hasExpiration() {return has(UInt32.Expiration);} - public boolean hasDestinationTag() {return has(UInt32.DestinationTag);} - public boolean hasInvoiceID() {return has(Hash256.InvoiceID);} - - public void expiration(UInt32 val) { put(UInt32.Expiration, val);} - public void destinationTag(UInt32 val) { put(UInt32.DestinationTag, val);} - public void invoiceID(Hash256 val) { put(Hash256.InvoiceID, val);} - public void sendMax(Amount val) { put(Amount.SendMax, val);} - public void destination(AccountID val) { put(AccountID.Destination, val); } - - public AccountID destination() {return get(AccountID.Destination);} - public UInt32 expiration() {return get(UInt32.Expiration);} - public UInt32 destinationTag() {return get(UInt32.DestinationTag);} - public Hash256 invoiceID() {return get(Hash256.InvoiceID);} - public Amount sendMax() {return get(Amount.SendMax);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/DepositPreauth.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/DepositPreauth.java deleted file mode 100644 index 68f08cde32..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/DepositPreauth.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class DepositPreauth extends Transaction { - public DepositPreauth() { - super(TransactionType.DepositPreauth); - } - public boolean hasAuthorize() {return has(AccountID.Authorize);} - public AccountID authorize() {return get(AccountID.Authorize);} - public void authorize(AccountID val) { put(AccountID.Authorize, val);} - - public boolean hasUnauthorize() {return has(AccountID.Unauthorize);} - public AccountID unauthorize() {return get(AccountID.Unauthorize);} - public void unauthorize(AccountID val) { put(AccountID.Unauthorize, val);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowCancel.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowCancel.java deleted file mode 100644 index ab435bacda..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowCancel.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class EscrowCancel extends Transaction { - public EscrowCancel() { - super(TransactionType.EscrowCancel); - } - - public UInt32 offerSequence() {return get(UInt32.OfferSequence);} - public void offerSequence(UInt32 val) { put(UInt32.OfferSequence, val);} - public AccountID owner() {return get(AccountID.Owner);} - public void owner(AccountID val) { put(AccountID.Owner, val);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowCreate.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowCreate.java deleted file mode 100644 index 3587a6c191..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowCreate.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class EscrowCreate extends Transaction { - public EscrowCreate() { - super(TransactionType.EscrowCreate); - } - public boolean hasDestinationTag() {return has(UInt32.DestinationTag);} - public boolean hasCancelAfter() {return has(UInt32.CancelAfter);} - public boolean hasFinishAfter() {return has(UInt32.FinishAfter);} - public boolean hasCondition() {return has(Blob.Condition);} - - public Amount amount() {return get(Amount.Amount);} - public AccountID destination() {return get(AccountID.Destination);} - public UInt32 destinationTag() {return get(UInt32.DestinationTag);} - public UInt32 cancelAfter() {return get(UInt32.CancelAfter);} - public UInt32 finishAfter() {return get(UInt32.FinishAfter);} - public Blob condition() {return get(Blob.Condition);} - - public void destinationTag(UInt32 val) { put(UInt32.DestinationTag, val);} - public void cancelAfter(UInt32 val) { put(UInt32.CancelAfter, val);} - public void finishAfter(UInt32 val) { put(UInt32.FinishAfter, val);} - public void amount(Amount val) { put(Amount.Amount, val);} - public void condition(Blob val) { put(Blob.Condition, val);} - public void destination(AccountID val) { put(AccountID.Destination, val);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowFinish.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowFinish.java deleted file mode 100644 index 7b552e768d..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/EscrowFinish.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class EscrowFinish extends Transaction { - public EscrowFinish() { - super(TransactionType.EscrowFinish); - } - - public boolean hasFulfillment() {return has(Blob.Fulfillment);} - public boolean hasCondition() {return has(Blob.Condition);} - - public UInt32 offerSequence() {return get(UInt32.OfferSequence);} - public Blob fulfillment() {return get(Blob.Fulfillment);} - public Blob condition() {return get(Blob.Condition);} - public AccountID owner() {return get(AccountID.Owner);} - - public void offerSequence(UInt32 val) { put(UInt32.OfferSequence, val);} - public void fulfillment(Blob val) { put(Blob.Fulfillment, val);} - public void condition(Blob val) { put(Blob.Condition, val);} - public void owner(AccountID val) { put(AccountID.Owner, val);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/OfferCancel.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/OfferCancel.java deleted file mode 100644 index 461d6cac09..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/OfferCancel.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class OfferCancel extends Transaction { - public OfferCancel() { - super(TransactionType.OfferCancel); - } - public UInt32 offerSequence() {return get(UInt32.OfferSequence);} - public void offerSequence(UInt32 val) { put(UInt32.OfferSequence, val);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/OfferCreate.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/OfferCreate.java deleted file mode 100644 index dbb783479d..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/OfferCreate.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class OfferCreate extends Transaction { - public OfferCreate() { - super(TransactionType.OfferCreate); - } - public UInt32 expiration() {return get(UInt32.Expiration);} - public UInt32 offerSequence() {return get(UInt32.OfferSequence);} - public Amount takerPays() {return get(Amount.TakerPays);} - public Amount takerGets() {return get(Amount.TakerGets);} - public void expiration(UInt32 val) {put(Field.Expiration, val);} - public void offerSequence(UInt32 val) {put(Field.OfferSequence, val);} - public void takerPays(Amount val) {put(Field.TakerPays, val);} - public void takerGets(Amount val) {put(Field.TakerGets, val);} - - public boolean hasExpiration() {return has(UInt32.Expiration);} - public boolean hasOfferSequence() {return has(UInt32.OfferSequence);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/Payment.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/Payment.java deleted file mode 100644 index 10da726d71..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/Payment.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.PathSet; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class Payment extends Transaction { - public Payment() { - super(TransactionType.Payment); - } - - public UInt32 destinationTag() {return get(UInt32.DestinationTag);} - public Hash256 invoiceID() {return get(Hash256.InvoiceID);} - public Amount amount() {return get(Amount.Amount);} - public Amount sendMax() {return get(Amount.SendMax);} - public AccountID destination() {return get(AccountID.Destination);} - - public PathSet paths() {return get(PathSet.Paths);} - public Amount deliverMin() {return get(Amount.DeliverMin);} - - public void destinationTag(UInt32 val) {put(Field.DestinationTag, val);} - public void invoiceID(Hash256 val) {put(Field.InvoiceID, val);} - public void deliverMin(Amount val) { put(Amount.DeliverMin, val);} - public void amount(Amount val) {put(Field.Amount, val);} - public void sendMax(Amount val) {put(Field.SendMax, val);} - public void destination(AccountID val) {put(Field.Destination, val);} - public void paths(PathSet val) {put(Field.Paths, val);} - - public boolean hasDestinationTag() {return has(UInt32.DestinationTag);} - public boolean hasInvoiceID() {return has(Hash256.InvoiceID);} - public boolean hasSendMax() {return has(Amount.SendMax);} - public boolean hasDeliverMin() {return has(Amount.DeliverMin);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelClaim.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelClaim.java deleted file mode 100644 index 8632dbd8b3..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelClaim.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class PaymentChannelClaim extends Transaction { - public PaymentChannelClaim() { - super(TransactionType.PaymentChannelClaim); - } - - public boolean hasAmount() {return has(Amount.Amount);} - public boolean hasBalance() {return has(Amount.Balance);} - public boolean hasPublicKey() {return has(Blob.PublicKey);} - public boolean hasSignature() {return has(Blob.Signature);} - - public void channel(Hash256 val) { put(Hash256.Channel, val);} - public void amount(Amount val) { put(Amount.Amount, val);} - public void balance(Amount val) { put(Amount.Balance, val);} - public void publicKey(Blob val) { put(Blob.PublicKey, val);} - public void signature(Blob val) { put(Blob.Signature, val);} - - public Hash256 channel() {return get(Hash256.Channel);} - public Amount amount() {return get(Amount.Amount);} - public Amount balance() {return get(Amount.Balance);} - public Blob publicKey() {return get(Blob.PublicKey);} - public Blob signature() {return get(Blob.Signature);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelCreate.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelCreate.java deleted file mode 100644 index 2475c637e5..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelCreate.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.Blob; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class PaymentChannelCreate extends Transaction { - public PaymentChannelCreate() { - super(TransactionType.PaymentChannelCreate); - } - - public boolean hasDestinationTag() {return has(UInt32.DestinationTag);} - public boolean hasCancelAfter() {return has(UInt32.CancelAfter);} - - public void destinationTag(UInt32 val) { put(UInt32.DestinationTag, val);} - public void cancelAfter(UInt32 val) { put(UInt32.CancelAfter, val);} - public void settleDelay(UInt32 val) { put(UInt32.SettleDelay, val);} - public void amount(Amount val) { put(Amount.Amount, val);} - public void publicKey(Blob val) { put(Blob.PublicKey, val);} - public void destination(AccountID val) { put(AccountID.Destination, val);} - - public UInt32 destinationTag() {return get(UInt32.DestinationTag);} - public UInt32 cancelAfter() {return get(UInt32.CancelAfter);} - public UInt32 settleDelay() {return get(UInt32.SettleDelay);} - public Amount amount() {return get(Amount.Amount);} - public Blob publicKey() {return get(Blob.PublicKey);} - public AccountID destination() {return get(AccountID.Destination);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelFund.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelFund.java deleted file mode 100644 index 85ccff90f4..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/PaymentChannelFund.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class PaymentChannelFund extends Transaction { - public PaymentChannelFund() { - super(TransactionType.PaymentChannelFund); - } - public boolean hasExpiration() {return has(UInt32.Expiration);} - - public UInt32 expiration() {return get(UInt32.Expiration);} - public Hash256 channel() {return get(Hash256.Channel);} - public Amount amount() {return get(Amount.Amount);} - - public void expiration(UInt32 val) { put(UInt32.Expiration, val);} - public void channel(Hash256 val) { put(Hash256.Channel, val);} - public void amount(Amount val) { put(Amount.Amount, val);} - -} - diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/SetRegularKey.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/SetRegularKey.java deleted file mode 100644 index 5d139c75f9..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/SetRegularKey.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class SetRegularKey extends Transaction { - public SetRegularKey() { - super(TransactionType.SetRegularKey); - } - public boolean hasRegularKey() {return has(AccountID.RegularKey);} - public AccountID regularKey() {return get(AccountID.RegularKey);} - public void regularKey(AccountID val) { put(AccountID.RegularKey, val);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/SignerListSet.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/SignerListSet.java deleted file mode 100644 index 830381f8ff..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/SignerListSet.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.STArray; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class SignerListSet extends Transaction { - public SignerListSet() { - super(TransactionType.SignerListSet); - } - public boolean hasSignerEntries() {return has(STArray.SignerEntries);} - - public UInt32 signerQuorum() {return get(UInt32.SignerQuorum);} - public STArray signerEntries() {return get(STArray.SignerEntries);} - - public void signerQuorum(UInt32 val) { put(UInt32.SignerQuorum, val);} - public void signerEntries(STArray val) { put(STArray.SignerEntries, val);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TicketCancel.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TicketCancel.java deleted file mode 100644 index a75f525e52..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TicketCancel.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class TicketCancel extends Transaction { - public TicketCancel() { - super(TransactionType.TicketCancel); - } - public Hash256 ticketID() { - return get(Hash256.TicketID); - } - public void ticketID(Hash256 id) { - put(Hash256.TicketID, id); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TicketCreate.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TicketCreate.java deleted file mode 100644 index 4e8ac5fb3f..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TicketCreate.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.AccountID; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class TicketCreate extends Transaction { - public TicketCreate() { - super(TransactionType.TicketCreate); - } - - public boolean hasExpiration() {return has(UInt32.Expiration);} - public boolean hasTarget() {return has(AccountID.Target);} - - public UInt32 expiration() {return get(UInt32.Expiration);} - public AccountID target() {return get(AccountID.Target);} - - public void expiration(UInt32 val) { put(UInt32.Expiration, val);} - public void target(AccountID val) { put(AccountID.Target, val);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TrustSet.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TrustSet.java deleted file mode 100644 index ba5702c2e4..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/TrustSet.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.ripple.core.types.known.tx.txns; - -import com.ripple.core.coretypes.Amount; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class TrustSet extends Transaction { - public TrustSet() { - super(TransactionType.TrustSet); - } - - public UInt32 qualityIn() {return get(UInt32.QualityIn);} - public UInt32 qualityOut() {return get(UInt32.QualityOut);} - public Amount limitAmount() {return get(Amount.LimitAmount);} - public void qualityIn(UInt32 val) {put(Field.QualityIn, val);} - public void qualityOut(UInt32 val) {put(Field.QualityOut, val);} - public void limitAmount(Amount val) {put(Field.LimitAmount, val);} - - public boolean hasQualityIn() {return has(UInt32.QualityIn);} - public boolean hasQualityOut() {return has(UInt32.QualityOut);} - public boolean hasLimitAmount() {return has(Amount.LimitAmount);} - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/pseudo/EnableAmendment.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/pseudo/EnableAmendment.java deleted file mode 100644 index b65fc929e6..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/pseudo/EnableAmendment.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ripple.core.types.known.tx.txns.pseudo; - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class EnableAmendment extends Transaction { - public EnableAmendment() { - super(TransactionType.EnableAmendment); - } - - public UInt32 ledgerSequence() {return get(UInt32.LedgerSequence);} - public void ledgerSequence(UInt32 val) { put(UInt32.LedgerSequence, val);} - public Hash256 amendment() {return get(Hash256.Amendment);} - public void amendment(Hash256 val) { put(Hash256.Amendment, val);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/pseudo/SetFee.java b/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/pseudo/SetFee.java deleted file mode 100644 index 800f13e35f..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/known/tx/txns/pseudo/SetFee.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.ripple.core.types.known.tx.txns.pseudo; - -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.serialized.enums.TransactionType; -import com.ripple.core.types.known.tx.Transaction; - -public class SetFee extends Transaction { - public SetFee() { - super(TransactionType.SetFee); - } - public boolean hasLedgerSequence() {return has(UInt32.LedgerSequence);} - - public void ledgerSequence(UInt32 val) { put(UInt32.LedgerSequence, val);} - public void referenceFeeUnits(UInt32 val) { put(UInt32.ReferenceFeeUnits, val);} - public void reserveBase(UInt32 val) { put(UInt32.ReserveBase, val);} - public void reserveIncrement(UInt32 val) { put(UInt32.ReserveIncrement, val);} - public void baseFee(UInt64 val) { put(UInt64.BaseFee, val);} - - public UInt32 ledgerSequence() {return get(UInt32.LedgerSequence);} - public UInt32 referenceFeeUnits() {return get(UInt32.ReferenceFeeUnits);} - public UInt32 reserveBase() {return get(UInt32.ReserveBase);} - public UInt32 reserveIncrement() {return get(UInt32.ReserveIncrement);} - public UInt64 baseFee() {return get(UInt64.BaseFee);} -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/ledger/LedgerHeader.java b/ripple-core/src/main/java/com/ripple/core/types/ledger/LedgerHeader.java deleted file mode 100644 index 240b0f6da8..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/ledger/LedgerHeader.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.ripple.core.types.ledger; - -import com.ripple.core.binary.STReader; -import com.ripple.core.coretypes.RippleDate; -import com.ripple.core.coretypes.hash.HalfSha512; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.coretypes.uint.UInt64; -import com.ripple.core.coretypes.uint.UInt8; -import com.ripple.core.serialized.BinaryParser; -import com.ripple.core.serialized.BytesSink; -import org.json.JSONObject; -import org.json.JSONWriter; - -import java.util.Date; - -public class LedgerHeader { - // Always 0x4C475200 (LWR) (Secures signed objects) - public UInt32 version = HashPrefix.ledgerMaster.uInt32(); - - public UInt32 sequence; // Ledger Sequence (0 for genesis ledger) - public UInt64 totalXRP; // - public Hash256 previousLedger; // The hash of the previous ledger (0 for genesis ledger) - public Hash256 transactionHash; // The hash of the transaction tree's root node. - public Hash256 stateHash; // The hash of the state tree's root node. - public UInt32 parentCloseTime; // The time the previous ledger closed - public UInt32 closeTime; // UTC minute ledger closed encoded as seconds since 1/1/2000 (or 0 for genesis ledger) - public UInt8 closeResolution; // The resolution (in seconds) of the close time - public UInt8 closeFlags; // Flags - - public Date closeDate; - - public static LedgerHeader fromParser(BinaryParser parser) { - return fromReader(new STReader(parser)); - } - public static LedgerHeader fromHex(String ledger_data) { - return LedgerHeader.fromParser(new BinaryParser(ledger_data)); - } - - public static LedgerHeader fromReader(STReader reader) { - LedgerHeader ledger = new LedgerHeader(); - - ledger.sequence = reader.uInt32(); - ledger.totalXRP = reader.uInt64(); - ledger.previousLedger = reader.hash256(); - ledger.transactionHash= reader.hash256(); - ledger.stateHash = reader.hash256(); - ledger.parentCloseTime = reader.uInt32(); - ledger.closeTime = reader.uInt32(); - ledger.closeResolution = reader.uInt8(); - ledger.closeFlags = reader.uInt8(); - - ledger.closeDate = RippleDate.fromSecondsSinceRippleEpoch(ledger.closeTime); - - return ledger; - } - - public void toBytesSink(BytesSink sink) { - sequence.toBytesSink(sink); - totalXRP.toBytesSink(sink); - previousLedger.toBytesSink(sink); - transactionHash.toBytesSink(sink); - stateHash.toBytesSink(sink); - parentCloseTime.toBytesSink(sink); - closeTime.toBytesSink(sink); - closeResolution.toBytesSink(sink); - closeFlags.toBytesSink(sink); - } - - public Hash256 hash() { - HalfSha512 half = HalfSha512.prefixed256(HashPrefix.ledgerMaster); - toBytesSink(half); - return half.finish(); - } - - public void toJSONWriter(JSONWriter writer) { - writer.key("ledger_index"); - writer.value(sequence.toJSON()); - writer.key("total_coins"); - writer.value(totalXRP.toString(10)); - writer.key("parent_hash"); - writer.value(previousLedger.toJSON()); - writer.key("transaction_hash"); - writer.value(transactionHash.toJSON()); - writer.key("account_hash"); - writer.value(stateHash.toJSON()); - writer.key("close_time"); - writer.value(closeTime.toJSON()); - writer.key("close_time_human"); - // TODO - writer.value(RippleDate.gmtString(RippleDate.fromSecondsSinceRippleEpoch(closeTime))); - writer.key("parent_close_time"); - writer.value(parentCloseTime.toJSON()); - writer.key("close_time_resolution"); - writer.value(closeResolution.toJSON()); - writer.key("close_flags"); - writer.value(closeFlags.toJSON()); - } - - public JSONObject toJSON() { - StringBuilder builder = new StringBuilder(); - JSONWriter jsonWriter = new JSONWriter(builder); - jsonWriter.object(); - toJSONWriter(jsonWriter); - jsonWriter.endObject(); - return new JSONObject(builder.toString()); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/AccountState.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/AccountState.java deleted file mode 100644 index c6eaadd3fa..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/AccountState.java +++ /dev/null @@ -1,341 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.TreeNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.ripple.core.coretypes.STObject; -import com.ripple.core.coretypes.Vector256; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.Index; -import com.ripple.core.coretypes.uint.UInt32; -import com.ripple.core.serialized.enums.LedgerEntryType; -import com.ripple.core.types.known.sle.LedgerEntry; -import com.ripple.core.types.known.sle.LedgerHashes; -import com.ripple.core.types.known.sle.entries.DirectoryNode; -import com.ripple.core.types.known.sle.entries.OfferDirectory; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONTokener; -import org.json.JSONWriter; - -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.Reader; -import java.util.Iterator; - -public class AccountState extends ShaMap { - public AccountState() { - super(); - } - public AccountState(boolean isCopy, int depth) { - super(isCopy, depth); - } - - @Override - protected ShaMapInner makeInnerOfSameClass(int depth) { - return new AccountState(true, depth); - } - - private static LedgerHashes newSkipList(Hash256 skipIndex) { - LedgerHashes skip; - skip = new LedgerHashes(); - skip.put(UInt32.Flags, new UInt32(0)); - skip.hashes(new Vector256()); - skip.index(skipIndex); - return skip; - } - - public void updateSkipLists(long currentIndex, Hash256 parentHash) { - long prev = currentIndex - 1; - - if ((prev & 0xFF) == 0) { - Hash256 skipIndex = Index.ledgerHashes(prev); - LedgerHashes skip = createOrUpdateSkipList(skipIndex); - Vector256 hashes = skip.hashes(); - assert hashes.size() <= 256; - hashes.add(parentHash); - skip.put(UInt32.LastLedgerSequence, new UInt32(prev)); - } - - Hash256 skipIndex = Index.ledgerHashes(); - LedgerHashes skip = createOrUpdateSkipList(skipIndex); - Vector256 hashes = skip.hashes(); - - if (hashes.size() > 256) throw new AssertionError(); - if (hashes.size() == 256) { - hashes.remove(0); - } - - hashes.add(parentHash); - skip.put(UInt32.LastLedgerSequence, new UInt32(prev)); - } - - private LedgerHashes createOrUpdateSkipList(Hash256 skipIndex) { - PathToIndex path = pathToIndex(skipIndex); - ShaMapInner top = path.dirtyOrCopyInners(); - LedgerEntryItem item; - - if (path.hasMatchedLeaf()) { - ShaMapLeaf leaf = path.invalidatedPossiblyCopiedLeafForUpdating(); - item = (LedgerEntryItem) leaf.item; - } else { - item = new LedgerEntryItem(newSkipList(skipIndex)); - top.addLeafToTerminalInner(new ShaMapLeaf(skipIndex, item)); - } - return (LedgerHashes) item.entry; - } - - public boolean addLE(LedgerEntry entry) { - LedgerEntryItem item = new LedgerEntryItem(entry); - return addItem(entry.index(), item); - } - - public boolean updateLE(LedgerEntry entry) { - LedgerEntryItem item = new LedgerEntryItem(entry); - return updateItem(entry.index(), item); - } - - public LedgerEntry getLE(Hash256 index) { - LedgerEntryItem item = (LedgerEntryItem) getItem(index); - return item == null ? null : item.value(); - } - - public DirectoryNode getDirectoryNode(Hash256 index) { - return (DirectoryNode) getLE(index); - } - - public Iterable offerDirectories(Hash256 bookBase) { - final QualityIterator iter = qualityIterator(bookBase); - - return new Iterable() { - @Override - public Iterator iterator() { - return new Iterator() { - @Override - public boolean hasNext() { - boolean hasNext = iter.hasNext(); - // In case we need to skip some entries - if (hasNext && !(iter.next() instanceof OfferDirectory)) { - return this.hasNext(); - } - return hasNext; - } - - @Override - public OfferDirectory next() { - return (OfferDirectory) iter.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - }; - } - - public void writeEntriesArray(final JSONWriter writer) { - writer.array(); - walkEntries(entry -> writer.value(entry.toJSON())); - writer.endArray(); - } - - // Assumes shamap won't be modified during iteration, not unusual for an - // iterator. - public class QualityIterator implements Iterator { - ShaMapInner[] inners = new ShaMapInner[64]; - Hash256 base; - Hash256 end; - int[] selections = new int[64]; - int commonNibblets; - int depth; - ShaMapLeaf next; - private boolean finished = false; - - public QualityIterator(Hash256 start) { - base = start; - depth=0; - end = Index.bookEnd(start); - ShaMapInner inner = AccountState.this; - setInner(inner); - setSelected(start.nibblet(depth)); - findCommonNibblets(); - } - - private void findCommonNibblets() { - for (int i = 0; i < 64; i++) { - if (base.nibblet(i) == end.nibblet(i)) { - commonNibblets = i; - } else { - break; - } - } - } - - private void setInner(ShaMapInner inner) { - inners[depth] = inner; - } - - private void findNext() { - next = null; - - while (true) { - while (selected() > 15) { - depth--; - incrementSelection(); - } - if (depth < commonNibblets && selected() > end.nibblet(depth)) { - finished = true; - break; - } - ShaMapInner current = currentInner(); - ShaMapNode branch = current.getBranch(selected()); - if (branch == null) { - incrementSelection(); - } else if (branch.isInner()) { - depth++; - setInner(branch.asInner()); - setSelected(base.nibblet(depth)); - } else if (branch.isLeaf()) { - ShaMapLeaf leaf = branch.asLeaf(); - Hash256 leafIndex = leaf.index; - boolean leafIsOnPathToBase = depth < commonNibblets; - if ( leafIsOnPathToBase || leafIndex.compareTo(base) > 0 && - leafIndex.compareTo(end) < 0) { - next = leaf; - incrementSelection(); - } else { - finished = true; - } - break; - } - } - } - - private ShaMapInner currentInner() { - return inners[depth]; - } - private int selected() { - return selections[depth]; - } - private void setSelected(int nibblet) { - selections[depth] = nibblet; - } - private void incrementSelection() { - selections[depth]++; - } - - @Override - public boolean hasNext() { - findNext(); - return !finished && next != null; - } - @Override - public LedgerEntry next() { - // Just assume hasNext has been called - LedgerEntryItem item = (LedgerEntryItem) next.item; - return item.entry; - } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - public QualityIterator qualityIterator(final Hash256 bookBase) { - return new QualityIterator(bookBase); - } - - public Iterable directoryIterator(OfferDirectory forQuality) { - // TODO: create an actual iterator - Vector256 indexes = new Vector256(); - OfferDirectory cursor = forQuality; - - while (cursor != null) { - indexes.addAll(cursor.indexes()); - if (cursor.hasNextIndex()) { - LedgerEntry le = getLE(cursor.nextIndex()); - if (le instanceof OfferDirectory) { - cursor = (OfferDirectory) le; - } - else { - break; - } - } - else { - break; - } - } - return indexes; - } - - public void walkEntries(final LedgerEntryVisitor walker) { - walkLeaves(leaf -> { - LedgerEntryItem item = (LedgerEntryItem) leaf.item; - walker.onEntry(item.entry); - }); - } - - // TODO - public Hash256 getNextIndex(Hash256 nextIndex, Hash256 bookEnd) { - return null; - } - - @Override - public AccountState copy() { - return (AccountState) super.copy(); - } - - public static AccountState loadFromLedgerDump(String filePath) { - try { - FileReader reader = new FileReader(filePath); - return loadFromLedgerDump(reader); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - } - - public static AccountState loadFromLedgerDump(Reader reader) { - try { - JsonFactory jsonFactory = new JsonFactory(); - JsonParser parser = jsonFactory.createParser(reader); - JsonToken jsonToken = parser.nextToken(); - if (jsonToken != JsonToken.START_OBJECT) { - throw new AssertionError(); - } - - String accountState = "accountState"; - - // TODO: - while (parser.nextToken() != null) { - String currentName = parser.getCurrentName(); - if (currentName.equals(accountState)) { - break; - } - } - - if (!parser.getCurrentName().equals("accountState")) { - throw new IllegalStateException("No `accountState` field found!"); - } - - AccountState state = new AccountState(); - ObjectMapper mapper = new ObjectMapper(); - if (parser.nextToken() != JsonToken.START_ARRAY) { - throw new AssertionError(); - } - - while (parser.nextToken() != JsonToken.END_ARRAY) { - TreeNode treeNode = mapper.readTree(parser); - state.addLE((LedgerEntry) STObject.fromJacksonObject((ObjectNode) treeNode)); - } - return state; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/AccountStateBuilder.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/AccountStateBuilder.java deleted file mode 100644 index 1b43203db7..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/AccountStateBuilder.java +++ /dev/null @@ -1,320 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.fields.Field; -import com.ripple.core.types.known.sle.LedgerEntry; -import com.ripple.core.types.known.sle.ThreadedLedgerEntry; -import com.ripple.core.types.known.sle.entries.*; -import com.ripple.core.types.known.tx.result.AffectedNode; -import com.ripple.core.types.known.tx.result.TransactionResult; - -import java.util.*; - -public class AccountStateBuilder { - private AccountState state; - private AccountState previousState = null; - private long targetLedgerIndex; - public long nextTransactionIndex = 0; - private Hash256 targetAccountHash; - public long totalTransactions = 0; - - private TreeSet directoriesModifiedMoreThanOnceByTransaction = new TreeSet<>(); - private TreeSet directoriesModifiedByTransaction = new TreeSet<>(); - public TreeSet ledgerModifiedEntries = new TreeSet<>(); - public TreeSet ledgerDeletedEntries = new TreeSet<>(); - - public boolean sortedDirectories = true; - - public void resetModified() { - ledgerModifiedEntries.clear(); - ledgerDeletedEntries.clear(); - directoriesModifiedMoreThanOnceByTransaction.clear(); - } - - public AccountStateBuilder(AccountState state, long targetLedgerIndex) { - this.state = state; - setStateCheckPoint(); - this.targetLedgerIndex = targetLedgerIndex; - } - - public void onLedgerClose(long ledgerIndex, Hash256 accountHash, Hash256 parentHash) { - state.updateSkipLists(ledgerIndex, parentHash); - targetLedgerIndex = ledgerIndex; - targetAccountHash = accountHash; - nextTransactionIndex = 0; - } - - public void setStateCheckPoint() { - previousState = state.copy(); - } - - public void onTransaction(TransactionResult tr) { - System.out.println("adding tx: " + tr.hash); - if (tr.meta.transactionIndex().longValue() != nextTransactionIndex) throw new AssertionError(); - if (tr.ledgerIndex.longValue() != targetLedgerIndex + 1) throw new AssertionError(String.format("%d != %d", tr.ledgerIndex.longValue(), targetLedgerIndex + 1)); - nextTransactionIndex++; - totalTransactions++; - directoriesModifiedByTransaction = new TreeSet<>(); - - for (AffectedNode an : sortedAffectedNodes(tr)) { - Hash256 id = an.ledgerIndex(); - LedgerEntry le = an.nodeAsFinal(); - - if (an.isCreatedNode()) { - ledgerModifiedEntries.add(id); - ledgerDeletedEntries.remove(id); - le.setDefaults(); - state.addLE(le); - - if (le instanceof Offer) { - Offer offer = (Offer) le; - offer.setOfferDefaults(); // TODO / TODO - - for (Hash256 directory : offer.directoryIndexes()) { - DirectoryNode dn = getDirectoryForUpdating(directory); - Hash256 index = offer.index(); - addToDirectoryNode(dn, index); - } - } else if (le instanceof RippleState) { - RippleState state = (RippleState) le; - - for (Hash256 directory : state.directoryIndexes()) { - DirectoryNode dn = getDirectoryForUpdating(directory); - addToDirectoryNode(dn, state.index()); - } - } - - if (le instanceof IndexedLedgerEntry) { - IndexedLedgerEntry owned = (IndexedLedgerEntry) le; - for (Hash256 directory : owned.ownerDirectoryIndexes(tr.txn)) { - DirectoryNode dn = getDirectoryForUpdating(directory); - addToDirectoryNode(dn, owned.index()); - } - } - - if (le instanceof ThreadedLedgerEntry) { - ThreadedLedgerEntry tle = (ThreadedLedgerEntry) le; - tle.previousTxnID(tr.hash); - tle.previousTxnLgrSeq(tr.ledgerIndex); - } - } else if (an.isDeletedNode()) { - ledgerModifiedEntries.remove(id); - ledgerDeletedEntries.add(id); - directoriesModifiedMoreThanOnceByTransaction.remove(id); - state.removeLeaf(id); - if (le instanceof Offer) { - Offer offer = (Offer) le; - for (Hash256 directory : offer.directoryIndexes()) { - DirectoryNode dn = getDirectoryForUpdating(directory); - if (dn != null) { -// Hash256 index = offer.index(); - if (dn.owner() != null) { - deleteFromDirectoryUnstable(offer, dn); - } else { - deleteFromDirectoryStable(offer, dn); - } - } - } - } else if (le instanceof RippleState) { - RippleState state = (RippleState) le; - for (Hash256 directory : state.directoryIndexes()) { - DirectoryNode dn = getDirectoryForUpdating(directory); - if (dn != null) { - deleteFromDirectoryUnstable(le, dn); - } - } - } else if (le instanceof IndexedLedgerEntry) { - IndexedLedgerEntry owned = (IndexedLedgerEntry) le; - for (Hash256 directory : owned.ownerDirectoryIndexes(tr.txn)) { - DirectoryNode dn = getDirectoryForUpdating(directory); - deleteFromDirectoryUnstable(le, dn); - } - } - } else if (an.isModifiedNode()) { - ledgerModifiedEntries.add(id); - ShaMapLeaf leaf = state.getLeafForUpdating(id); - LedgerEntryItem item = (LedgerEntryItem) leaf.item; - LedgerEntry leModded = item.entry; - - if (le instanceof ThreadedLedgerEntry) { - ThreadedLedgerEntry tle = (ThreadedLedgerEntry) le; - tle.previousTxnID(tr.hash); - tle.previousTxnLgrSeq(tr.ledgerIndex); - } - for (Field field : le) { - // Already have the `index` - if (field == Field.LedgerIndex) { - continue; - } - leModded.put(field, le.get(field)); - } - // Find all removed fields - ArrayList removed = new ArrayList<>(); - for (Field field : leModded) { - if (an.removedField(field)) { - removed.add(field); - } - } - removed.forEach(leModded::remove); - } - } - } - - private void deleteFromDirectoryUnstable(LedgerEntry b4, DirectoryNode dn) { - boolean b = directoryRemoveUnstable(dn, b4.index()); - DirectoryNode cursor = dn; - - if (!b) { - while (cursor.indexNext() != null) { - cursor = getDirectoryForUpdating(cursor.nextIndex()); - b = directoryRemoveUnstable(cursor, b4.index()); - if (b) { - break; - } - } - } -// - if (!b) { - cursor = dn; - while (cursor.indexPrevious() != null) { - cursor = getDirectoryForUpdating(cursor.prevIndex()); - b = directoryRemoveUnstable(cursor, b4.index()); - if (b) { - break; - } - } - } - } - private void deleteFromDirectoryStable(LedgerEntry b4, DirectoryNode dn) { - boolean b = directoryRemoveStable(dn, b4.index()); - DirectoryNode cursor = dn; - - if (!b) { - while (cursor.hasIndexPrevious()) { - cursor = getDirectoryForUpdating(cursor.prevIndex()); - b = directoryRemoveStable(cursor, b4.index()); - if (b) { - break; - } - } - } - if (!b) { - while (cursor.indexNext() != null) { - cursor = getDirectoryForUpdating(cursor.nextIndex()); - b = directoryRemoveStable(cursor, b4.index()); - if (b) { - break; - } - } - } - } - - public static Collection makeCollection(Iterable iter) { - Collection list = new ArrayList<>(); - for (E item : iter) { - list.add(item); - } - return list; - } - private ArrayList sortedAffectedNodes(TransactionResult tr) { - ArrayList sorted = new ArrayList<>(makeCollection(tr.meta.affectedNodes())); - sorted.sort(Comparator.comparingInt(this::getOrdinal)); - return sorted; - } - - private int getOrdinal(AffectedNode o1) { - switch (o1.ledgerEntryType()) { - case DirectoryNode: - return 10; - case RippleState: - return 20; - case Offer: - return 30; - case Escrow: - case SignerList: - case PayChannel: - return 21; - default: - return 40; - } - } - - private void onDirectoryModified(DirectoryNode dn) { - Hash256 index = dn.index(); - if (directoriesModifiedByTransaction.contains(index)) { - directoriesModifiedMoreThanOnceByTransaction.add(index); - } - else { - directoriesModifiedByTransaction.add(index); - } - } - private boolean directoryRemoveStable(DirectoryNode dn, Hash256 index) { - onDirectoryModified(dn); - return dn.indexes().remove(index); - } - private boolean directoryRemoveUnstable(DirectoryNode dn, Hash256 index) { - onDirectoryModified(dn); - if (sortedDirectories) { - return dn.indexes().remove(index); - } else { - return dn.indexes().removeUnstable(index); - } - } - private void addToDirectoryNode(DirectoryNode dn, Hash256 index) { - onDirectoryModified(dn); - if (dn.exchangeRate() != null) { - dn.indexes().add(index); - } else { - dn.indexes().add(index); - if (sortedDirectories) { - Collections.sort(dn.indexes()); - } - } - } - private DirectoryNode getDirectoryForUpdating(Hash256 directoryIndex) { - ShaMapLeaf leaf = state.getLeafForUpdating(directoryIndex); - if (leaf == null) { - return null; - } - LedgerEntryItem lei = (LedgerEntryItem) leaf.item; - return (DirectoryNode) lei.entry; - } - - public AccountState state() { - return state; - } - - public long currentLedgerIndex() { - return targetLedgerIndex; - } - - public String targetAccountHashHex() { - return targetAccountHash.toHex(); - } - public Hash256 targetAccountHash() { - return targetAccountHash; - } - - public TreeSet directoriesWithIndexesOutOfOrder() { - TreeSet ret = new TreeSet<>(); - for (Hash256 hash256 : directoriesModifiedMoreThanOnceByTransaction) { - DirectoryNode dn = state.getDirectoryNode(hash256); - if (dn.owner() != null) { - ret.add(hash256); - } - } - return ret; - } - - public boolean bad() { - return !state.hash().equals(targetAccountHash); - } - - public AccountState previousState() { - return previousState; - } - - public void setState(AccountState map) { - state = map; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/BytesItem.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/BytesItem.java deleted file mode 100644 index 11365302b5..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/BytesItem.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.serialized.BytesSink; - -public class BytesItem extends ShaMapItem { - private byte[] item; - - public BytesItem(byte[] item) { - this.item = item; - } - - @Override - void toBytesSink(BytesSink sink) { - sink.add(item); - } - - @Override - public ShaMapItem copy() { - return this; - } - - @Override - public byte[] value() { - return item; - } - - @Override - public Prefix hashPrefix() { - return new Prefix() { - @Override - public byte[] bytes() { - return new byte[0]; - } - }; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/HashedTreeWalker.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/HashedTreeWalker.java deleted file mode 100644 index a565baf19b..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/HashedTreeWalker.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.Hash256; - -public interface HashedTreeWalker { - public void onLeaf(Hash256 h, ShaMapLeaf le); - public void onInner(Hash256 h, ShaMapInner inner); -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/LeafWalker.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/LeafWalker.java deleted file mode 100644 index 71f371cebe..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/LeafWalker.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.ripple.core.types.shamap; - -public interface LeafWalker { - void onLeaf(ShaMapLeaf shaMapLeaf); -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/LedgerEntryItem.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/LedgerEntryItem.java deleted file mode 100644 index ea67d4bf0b..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/LedgerEntryItem.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.STObject; -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.fields.Field; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.types.known.sle.LedgerEntry; - -public class LedgerEntryItem extends ShaMapItem { - public LedgerEntryItem(LedgerEntry entry) { - this.entry = entry; - } - - public LedgerEntry entry; - - @Override - void toBytesSink(BytesSink sink) { - entry.toBytesSink(sink); - } - - @Override - public String toString() { - return entry.prettyJSON(); - } - - @Override - public ShaMapItem copy() { - STObject object = STObject.fromBytes(entry.toBytes()); - LedgerEntry le = (LedgerEntry) object; - // TODO: what about other auxiliary (non serialized) fields - le.index(entry.index()); - return new LedgerEntryItem(le); - } - - @Override - public LedgerEntry value() { - return entry; - } - - @Override - public Prefix hashPrefix() { - return HashPrefix.leafNode; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/LedgerEntryVisitor.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/LedgerEntryVisitor.java deleted file mode 100644 index 6ce639b552..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/LedgerEntryVisitor.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.types.known.sle.LedgerEntry; - -public interface LedgerEntryVisitor { - public void onEntry(LedgerEntry entry); -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/NodeStore.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/NodeStore.java deleted file mode 100644 index 8d1bd98eee..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/NodeStore.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.HalfSha512; -import com.ripple.core.coretypes.hash.Hash256; - -/** - - * This is a toy implementation for illustrative purposes. - */ -public class NodeStore { - /** - * In ripple, all data is stored in a simple binary key/value database. - * The keys are 256 bit binary strings and the values are binary strings of - * arbitrary length. - */ - public static interface KeyValueBackend { - void put(Hash256 key, byte[] content); - byte[] get(Hash256 key); - } - - KeyValueBackend backend; - public NodeStore(KeyValueBackend backend) { - this.backend = backend; - } - /** - * All data stored is keyed by the hash of it's contents. - * Ripple uses the first 256 bits of a sha512 as it's 33 percent - * faster than using sha256. - * - * @return `key` used to store the content - */ - private Hash256 storeContent(byte[] content) { - HalfSha512 hasher = new HalfSha512(); - hasher.update(content); - Hash256 key = hasher.finish(); - storeHashKeyedContent(key, content); - return key; - } - - /** - * @param hash As ripple uses the `hash` of the contents as the - * NodeStore key, `hash` is pervasively used in lieu of - * the term `key`. - */ - private void storeHashKeyedContent(Hash256 hash, byte[] content) { - // Note: The real nodestore actually prepends some metadata, which doesn't - // contribute to the hash. - backend.put(hash, content); // metadata + content - } - - /** - * The complement to `set` api, which together form a simple public interface. - */ - public byte[] get(Hash256 hash) { - return backend.get(hash); - - } - /** - * The complement to `get` api, which together form a simple public interface. - */ - public Hash256 set(byte[] content) { - return storeContent(content); - } -} \ No newline at end of file diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/PathToIndex.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/PathToIndex.java deleted file mode 100644 index dcc0dfd56e..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/PathToIndex.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.Hash256; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Iterator; - -public class PathToIndex { - public Hash256 index; - public ShaMapLeaf leaf; - - private ArrayDeque inners; - private ShaMapInner[] dirtied; - private boolean matched = false; - - public boolean hasLeaf() { - return leaf != null; - } - public boolean leafMatchedIndex() { - return matched; - } - public boolean copyLeafOnUpdate() { - return leaf.version != dirtied[0].version; - } - - int size() { - return inners.size(); - } - - public ShaMapInner top() { - return dirtied[dirtied.length - 1]; - } - - // returns the - public ShaMapInner dirtyOrCopyInners() { - if (maybeCopyOnWrite()) { - int ix = 0; - // We want to make a uniformly accessed array of the inners - dirtied = new ShaMapInner[inners.size()]; - // from depth 0 to 1, to 2, to 3, don't be fooled by the api - Iterator it = inners.descendingIterator(); - - // This is actually the root which COULD be the top of the stack - // Think about it ;) - ShaMapInner top = it.next(); - dirtied[ix++] = top; - top.invalidate(); - - while (it.hasNext()) { - ShaMapInner next = it.next(); - boolean doCopies = next.version != top.version; - - if (doCopies) { - ShaMapInner copy = next.copy(top.version); - copy.invalidate(); - top.setBranch(index, copy); - next = copy; - } else { - next.invalidate(); - } - top = next; - dirtied[ix++] = top; - } - return top; - } else { - copyInnersToDirtiedArray(); - return inners.peekFirst(); - } - } - - public boolean hasMatchedLeaf() { - return hasLeaf() && leafMatchedIndex(); - } - - public void collapseOnlyLeafChildInners() { - assert dirtied != null; - - ShaMapInner next; - ShaMapLeaf onlyChild = null; - - for (int i = dirtied.length - 1; i >= 0; i--) { - next = dirtied[i]; - if (onlyChild != null) { - next.setLeaf(onlyChild); - } - onlyChild = next.onlyChildLeaf(); - if (onlyChild == null) { - break; - } - } - } - - private void copyInnersToDirtiedArray() { - int ix = 0; - dirtied = new ShaMapInner[inners.size()]; - Iterator descending = inners.descendingIterator(); - while (descending.hasNext()) { - ShaMapInner next = descending.next(); - dirtied[ix++] = next; - next.invalidate(); - } - } - - private boolean maybeCopyOnWrite() { - return inners.peekLast().doCoW; - } - - public PathToIndex(ShaMapInner root, Hash256 index) { - this.index = index; - makeStack(root, index); - } - - private void makeStack(ShaMapInner root, Hash256 index) { - inners = new ArrayDeque<>(); - ShaMapInner top = root; - - while (true) { - inners.push(top); - ShaMapNode existing = top.getBranch(index); - if (existing == null) { - break; - } else if (existing.isLeaf()) { - leaf = existing.asLeaf(); - matched = leaf.index.equals(index); - break; - } - else if (existing.isInner()) { - top = existing.asInner(); - } - } - } - - public ShaMapLeaf invalidatedPossiblyCopiedLeafForUpdating() { - assert matched; - if (dirtied == null) { - dirtyOrCopyInners(); - } - ShaMapLeaf theLeaf = leaf; - - if (copyLeafOnUpdate()) { - theLeaf = leaf.copy(); - top().setLeaf(theLeaf); - } - theLeaf.invalidate(); - return theLeaf; - } - - public ArrayList topDownList() { - ArrayList path = new ArrayList<>(); - Iterator shaMapInnerIterator = inners.descendingIterator(); - shaMapInnerIterator.forEachRemaining(path::add); - path.add(leaf); - return path; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/README.md b/ripple-core/src/main/java/com/ripple/core/types/shamap/README.md deleted file mode 100644 index a78bc3ad73..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/README.md +++ /dev/null @@ -1,319 +0,0 @@ -Rippled NodeStore ------------------ - -To understand a ShaMap first you must know about the NodeStore. - -```java -/** - - * This is a toy implementation for illustrative purposes. - */ -public class NodeStore { - /** - * In ripple, all data is stored in a simple binary key/value database. - * The keys are 256 bit binary strings and the values are binary strings of - * arbitrary length. - */ - public interface KeyValueBackend { - void put(Hash256 key, byte[] content); - byte[] get(Hash256 key); - } - - KeyValueBackend backend; - public NodeStore(KeyValueBackend backend) { - this.backend = backend; - } - /** - * All data stored is keyed by the hash of it's contents. - * Ripple uses the first 256 bits of a sha512 as it's 33 percent - * faster than using sha256. - * - * @return `key` used to store the content - */ - private Hash256 storeContent(byte[] content) { - Hash256.HalfSha512 hasher = new Hash256.HalfSha512(); - hasher.update(content); - Hash256 key = hasher.finish(); - storeHashKeyedContent(key, content); - return key; - } - - /** - * @param hash As ripple uses the `hash` of the contents as the - * NodeStore key, `hash` is pervasively used in lieu of - * the term `key`. - */ - private void storeHashKeyedContent(Hash256 hash, byte[] content) { - // Note: The real NodeStore actually prepends some metadata, which doesn't - // contribute to the hash. - backend.put(hash, content); // metadata + content - } - - /** - * The complement to `set` api, which together form a simple public interface. - */ - public byte[] get(Hash256 hash) { - return backend.get(hash); - - } - /** - * The complement to `get` api, which together form a simple public interface. - */ - public Hash256 set(byte[] content) { - return storeContent(content); - } -} -``` - -See also: -* [Serialized Types](../../../../../../../../README.md) -* [BinaryFormats.txt (historical)](https://github.com/ripple/rippled/blob/07df5f1f81b0ee1ab641d134ba8e940a90f5297e/BinaryFormats.txt#L2-L6) - -Excerpt from BinaryFormats.txt (historical): - -
- All signed or hashed objects must have well-defined binary formats at the - byte level. These formats do not have to be the same as the network wire - formats or the forms used for efficient storage or human display. However, - it must always be possible to precisely re-create, byte for byte, any signed - or hashed object. Otherwise, the signatures or hashes cannot be validated. -
- -Note that currently (26/Jan/2018) the NodeStore stores it in the hashing form. - -What is a ShaMap? ------------------ - -A ShaMap is a special type of tree, used as a way to index values stored in a -`NodeStore` - -Recall that values in the NodeStore are keyed by the hash of their contents. - -But what about identities that change over time? How can you retrieve a certain -version of something? What could be used as an enduring identifier? The value -must have some component[s] that are static over time. These are fed into a -hashing function to create a 256 bit identifier. - -But how is this used? You can only query values by `hash` in the NodeStore. The -hash, as a function of a value would obviously change along with it. - -The identifier is used as an `index` into a ShaMap tree, which in ripple, is -representative of a point in time. In fact a ShaMap can be hashed -deterministically, thus a point in time can be identified by a `hash`. Where is -a ShaMap actually stored? In the NodeStore, of course. - -But the NodeStore only stores binary content you protest! But the ShaMap has a -binary representation! So what are the `contents` of a ShaMap, to be hashed to -be stored in the NodeStore? - -Glad you asked. A tree, has a root, and many children, either more branches, or -terminal leaves. The root, and any of its children that have children -themselves, are classed as `inner nodes`. - -In a ShaMap these `inner nodes` each have 16 `slots` for children. The binary -representation of these is simply 16 `hash`es, used to retrieve child nodes from -the NodeStore. - -An example of an inner node's `contents` - - Empty slots are represented as 32 0 bytes - - ``` - 022CC592F5D4ABC3A63DA2A036CDDC0825B30717C78EF287BEF200056133FDA2 - 0000000000000000000000000000000000000000000000000000000000000000 - BEE626551799DDFE65BD2D9A0F0EA24D72C93CFD8E083176718D2B079EC60214 - E1B34F1D9209CB668A50CCEE71C8109D140A6D715D923AEE98E6D53015D8B66B - 4C27A856094CFDE37CD2A0EA93DADB595B10CFEC55F816C987A6AC48D13AF5C0 - 2F770714A9EF92792F44AA1537C18F68AFE3FFF157FB9088FFE2BDA695C19B71 - C915CA982310CF41CF1266AA43C3B31ACBF4304D05ADB54A352D942C890763A3 - F29FAD442CE204513BEA555A4192E324407444D946449CEA510C37A9BB982134 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 44B3BE10744EA2DA010D530C6AE64E3C3984DA7701EE79A66EA429EC11B87D1D - 0C7BE8569E9F08BADDEB91EBE79E5B98BBF245B067B7B83B4A95430CFEC9F7E8 - BB38EA169DB6A020EA820BD1242DDB6250B397A26015507BA3C7F3C041EA683C - 0000000000000000000000000000000000000000000000000000000000000000 - 15B98934D22B5CB7233C42CE8DC8DD0D2328AB91CC574332C45D7160BD31D4AD - 1894E389AE4A63BA99C2D0546A58A976ECDAB14C09B98F532999B464696E29E5 - ``` - -What about those `index` thingies again? Remember, the `index` can't be used to -query something directly in the NodeStore. - -First you need a known point in time, which we learned could be defined by a -`hash` of a ShaMap. - -* shamap hash: 2F049AEE51C7C96AB911AF86E3278F4F90A38D196422A832617FF0C6F29C3704 -* value index: FEEE5CC92B64375C8FEE56D54A82B9965E44FE0DCF673DBF27D0AA93F8AFF4FB - -Imagine we have those above. First we query the NodeStore with `2F049A...` - -What do we expect back? A ShaMap hash is the hash of the binary representation -of the root node (which is an `inner node`) of the tree, so we'd expect -something in the form shown earlier, with 16 256 bit hashes. - -From the NodeStore we retrieve: - - ``` - 4D494E00 - 3C926652404076CA35EA1D89580975C50DCD0B29EC16079F168BE63BB1D6F237 - F0D3F178C56C438C597D53FF5E3D44EE3865C17A0D25DCC2460F20BE4BEB4B7E - D65D6CA291E451E6D6566A5431D755A749D9B0450E83B376148A6F93349ACD46 - 309B18093008055BE23E118DA4243DFC9AEF66E3260E2008BD6E6E6806F06725 - 0000000000000000000000000000000000000000000000000000000000000000 - D7D5A7C89DAE6D7517EDCB0601829CB339376EECD1DE8CA0216B6C18FFD77C32 - 7C33AEB525B09A7BC013C01015D59A88129A8A6C18B45E6D7D0CA1F13E6FCB44 - AB35119A7529D4BA57C41D7020833A0E9600895F61E9E9DEABBFDF82A877CBB2 - 3BE7A48570263034E6DCBEFA6ED57B63C0592AE145341A79DAF57C3BDF7C5793 - 17006AD967A1536E0441517990AD5B42890A3168AC9E045EAF9AC5A06F672561 - 94B12B8BB76D525DD9320AF51893A6D69FF730C1E647181E7A7B51FE1FBF369E - 0579A72B3209AE66840F6E8317F422F43937E6AE1A14C8B30243DE016551643F - B873AA4EF1CDF0332C59BE423256610C4C6A1D32E2A1F8DE748390E29E8FC2D3 - EB03A93BBC95ED56FB73E6FBDAB8F6916BBD1BD358336CBA4DA673558F5B068C - E64A78A07CF33C3B74AEA9862B5758513677324D4C9D72C69D14B4EB64D4EB02 - 4E8C0CE75693B85A4CAC5516B77702E660A55281CF17DBE09552941F3078D81C - ``` - -We see the hashes for 16 nodes clear as day, but what is this `4D494E00` -prefixed to the front? Converted to `ascii` letters the hex `4D494E00` is -`MIN\x00`, meaning sham)ap i)nner n)ode. - -The prefix `namespaces` the content, so different classes of objects which -would otherwise have the same binary representation, will have a different -`hash`. These `hash prefixes` serve another useful purpose, as we'll see later. -(Similarly, there are namespacing prefixes for an `index` (created by - feeding static components of an identity into a hashing function)) - -Is our value `index` hash amongst those enumerated? No !!! So what do we do with -it? An index, usually means an ordinal, defining a place in an array. The -`index` is actually an index into 64 arrays. Each nibble in the `index` is an -index into the 16 slots in each inner node. - -Consider again the value `index`: - - `FEEE5CC92B64375C8FEE56D54A82B9965E44FE0DCF673DBF27D0AA93F8AFF4FB` - -To use the `index` we take the first nibble, `F` (yes, we go left to right) - -The letter `F` in hex has the ordinal value 15, so we take the 16th branch (0 -based indexing) - -We select the 16th hash - - `4E8C0CE75693B85A4CAC5516B77702E660A55281CF17DBE09552941F3078D81C` - -From the NodeStore we retrieve: - - ``` - 4D494E00 - 0000000000000000000000000000000000000000000000000000000000000000 - 286BC64A4A369857E4B0B5834C54CED797110D06E64F7759FBA2C87D3630D418 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 60631B91674954D21314B89CEE3B7F740B4FB1374610498EA1A7A3B79BA6706D - 3B1C36A6311256FE1E72BEDBA2042D67D86F561E51B387CFA680BCE9982CD6DB - 5568E7032EE018CA484C181CD68411B623229D0666AE0038708500C59402A282 - 0000000000000000000000000000000000000000000000000000000000000000 - ``` - -There's that 'MIN\x00` hash prefix again. - -In fact, this prefix is how we can deterministically say that this is an -`inner node` and that we can interpret the following bytes as 16 more -`hash`es. - -We have descended deeper into the tree, but it seems we need to go deeper. We -are currently at a depth of 2, so to go deeper we need the 2nd nibble. - -value index: - ``` - FEEE5CC92B64375C8FEE56D54A82B9965E44FE0DCF673DBF27D0AA93F8AFF4FB` - | - \ - 2nd nibble - ``` - -The letter `E` in hex has the ordinal value 14, so we take the 15th branch (0 -based indexing) - -We select the 15th hash: - - `5568E7032EE018CA484C181CD68411B623229D0666AE0038708500C59402A282` - -From the NodeStore we retrieve: - - ``` - 534E4400 - C11C12000722800000002400AA7BFF201900AA7BFC201B0226FF9464D4461B5C - A191A906000000000000000000000000455448000000000006CC4A6D023E68AA - 3499C6DE3E9F2DC52B8BA25465400000000861014A6840000000000000C17321 - 03CF1DFB34A96363FF2B91638FCE51E6D7B88419729E9A81ABC99A9512FFB9C7 - 3374463044022005ED4635AE246A4060378D9396CAA0E42F7E9129D309B2E98B - DF5CE2352520A002207D4494019EA4327F4087EA34B05CE9E5CD0AE3082BAD77 - CC3698CE5A59C21BD581140BEC53D0830ADCE9E372086E570809916C440E83C3 - F4201C00000033F8E311006F561BF352EEDB9072286286A4BBC8C419C995A171 - 0AA0CFA8B0D2B843F07D294F60E82400AA7BFF501090B86A84C7F7843673BCF8 - 2E565E69498CAEF463F8055ABA4C04581E76C9270064D4461B5CA191A9060000 - 00000000000000000000455448000000000006CC4A6D023E68AA3499C6DE3E9F - 2DC52B8BA25465400000000861014A81140BEC53D0830ADCE9E372086E570809 - 916C440E83E1E1E31100645690B86A84C7F7843673BCF82E565E69498CAEF463 - F8055ABA4C04581E76C92700E8364C04581E76C927005890B86A84C7F7843673 - BCF82E565E69498CAEF463F8055ABA4C04581E76C92700011100000000000000 - 00000000004554480000000000021106CC4A6D023E68AA3499C6DE3E9F2DC52B - 8BA254E1E1E41100645690B86A84C7F7843673BCF82E565E69498CAEF463F805 - 5ABA4C045835BF30CCBFE72200000000364C045835BF30CCBF5890B86A84C7F7 - 843673BCF82E565E69498CAEF463F8055ABA4C045835BF30CCBF011100000000 - 00000000000000004554480000000000021106CC4A6D023E68AA3499C6DE3E9F - 2DC52B8BA2540311000000000000000000000000000000000000000004110000 - 000000000000000000000000000000000000E1E1E411006F56AE4B62A73DC540 - 40E523FCE863981AB601B6D6BCD3CFCE303B43A430FC6D4DB1E7220000000024 - 00AA7BFC250226FF9133000000000000000034000000000000000055B61A418E - 421021CFBA22B02F4934B4F79B1091F91CF910AEB21DA0975CEC52AB501090B8 - 6A84C7F7843673BCF82E565E69498CAEF463F8055ABA4C045835BF30CCBF64D4 - 461B7D5C693EDF000000000000000000000000455448000000000006CC4A6D02 - 3E68AA3499C6DE3E9F2DC52B8BA25465400000000861014A81140BEC53D0830A - DCE9E372086E570809916C440E83E1E1E511006456B937CC88FCAF18886CC8D4 - B19A5326F56B0B84E06AE511407B072DE348E05376E722000000003100000000 - 0000000032000000000000000058B937CC88FCAF18886CC8D4B19A5326F56B0B - 84E06AE511407B072DE348E0537682140BEC53D0830ADCE9E372086E57080991 - 6C440E83E1E1E5110061250226FF915558E2CDAB1D8D5477D8FED6EA5EFD9F9F - D8FAC56EA907E94197900C6C6175CE9556E9293AF964F2B20467530673C8F327 - 41ED03DEF52F6B6625B4D837C155856C26E62400AA7BFF6240000000B60EBC57 - E1E722000000002400AA7C002D000000186240000000B60EBB9681140BEC53D0 - 830ADCE9E372086E570809916C440E83E1E1F1031000 - FEEE5CC92B64375C8FEE56D54A82B9965E44FE0DCF673DBF27D0AA93F8AFF4FB - ``` - -Well, here's something new. The `hash prefix` is different. This time the -hex decodes as `MLN\x00`, meaning sham)ap l)eaf n)ode. - -And what's that at the end? Is that our index? It is!! - -Why does it need to be stored? We have only used `FE` to traverse to this -node. Without storing the `index` identifier in the leaf node contents, -there would be no way to be certain that this leaf held the item you wanted. -More importantly, it acts as further name-spacing, to prevent collisions. -(Technically, you could synthesize the index, by parsing the contents of -the object and recreating it) - -Takeaways ---------- - -* A `hash` keys the NodeStore -* An `index` is a path to an item in a ShaMap -* For communication purposes - - Always use `hash` when referring to a key for the NodeStore - - Always use `index` when referring to a key for a ShaMap - -Links ------ - -* [Rippled Hash Prefix declarations](../../coretypes/hash/prefixes/HashPrefix.java) \ No newline at end of file diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMap.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMap.java deleted file mode 100644 index 0129eb7c4a..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMap.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ripple.core.types.shamap; - -import java.util.concurrent.atomic.AtomicInteger; - -public class ShaMap extends ShaMapInner { - private AtomicInteger copies; - - public ShaMap() { - super(0); - // This way we can copy the first to the second, - // copy the second, then copy the first again ;) - copies = new AtomicInteger(); - } - public ShaMap(boolean isCopy, int depth) { - super(isCopy, depth, 0); - } - - @Override - protected ShaMapInner makeInnerOfSameClass(int depth) { - return new ShaMap(true, depth); - } - - public ShaMap copy() { - version = copies.incrementAndGet(); - ShaMap copy = (ShaMap) copy(copies.incrementAndGet()); - copy.copies = copies; - return copy; - } - -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapDiff.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapDiff.java deleted file mode 100644 index 41e23d50e1..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapDiff.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.Hash256; - -import java.util.TreeSet; - -public class ShaMapDiff { - public ShaMap one, two; - - public TreeSet modified = new TreeSet<>(); - public TreeSet deleted = new TreeSet<>(); - public TreeSet added = new TreeSet<>(); - - public ShaMapDiff(ShaMap one, ShaMap two) { - this.one = one; - this.two = two; - } - - // Find what's added, modified and deleted in `two` - public ShaMapDiff find() { - one.hash(); - two.hash(); - compare(one, two); - return this; - } - - public ShaMapDiff inverted() { - ShaMapDiff shaMapDiff = new ShaMapDiff(two, one); - - shaMapDiff.added = deleted; - shaMapDiff.modified = modified; - shaMapDiff.deleted = added; - - return shaMapDiff; - } - - public void apply(ShaMap sa) { - for (Hash256 mod : modified) { - boolean modded = sa.updateItem(mod, two.getItem(mod).copy()); - if (!modded) throw new AssertionError(); - } - - for (Hash256 add : added) { - boolean added = sa.addItem(add, two.getItem(add).copy()); - if (!added) throw new AssertionError(); - } - for (Hash256 delete : deleted) { - boolean removed = sa.removeLeaf(delete); - if (!removed) throw new AssertionError(); - } - } - private void compare(ShaMapInner a, ShaMapInner b) { - for (int i = 0; i < 16; i++) { - ShaMapNode aChild = a.getBranch(i); - ShaMapNode bChild = b.getBranch(i); - - if (aChild == null && bChild != null) { - trackAdded(bChild); - // added in B - } else if (aChild != null && bChild == null) { - trackRemoved(aChild); - // removed from B - } else if (aChild != null && !aChild.hash().equals(bChild.hash())) { - boolean aleaf = aChild.isLeaf(), - bLeaf = bChild.isLeaf(); - - if (aleaf && bLeaf) { - ShaMapLeaf la = (ShaMapLeaf) aChild; - ShaMapLeaf lb = (ShaMapLeaf) bChild; - if (la.index.equals(lb.index)) { - modified.add(la.index); - } else { - deleted.add(la.index); - added.add(lb.index); - } - } else if (aleaf /*&& bInner*/) { - ShaMapLeaf la = (ShaMapLeaf) aChild; - ShaMapInner ib = (ShaMapInner) bChild; - trackAdded(ib); - - //noinspection Duplicates - if (ib.hasLeaf(la.index)) { - // because trackAdded would have added it - added.remove(la.index); - ShaMapLeaf leaf = ib.getLeaf(la.index); - if (!leaf.hash().equals(la.hash())) { - modified.add(la.index); - } - } else { - deleted.add(la.index); - } - } else if (bLeaf /*&& aInner*/) { - ShaMapLeaf lb = (ShaMapLeaf) bChild; - ShaMapInner ia = (ShaMapInner) aChild; - trackRemoved(ia); - - //noinspection Duplicates - if (ia.hasLeaf(lb.index)) { - // because trackRemoved would have deleted it - deleted.remove(lb.index); - ShaMapLeaf leaf = ia.getLeaf(lb.index); - if (!leaf.hash().equals(lb.hash())) { - modified.add(lb.index); - } - } else { - added.add(lb.index); - } - } else /*if (aInner && bInner)*/ { - compare((ShaMapInner) aChild, (ShaMapInner) bChild); - } - } - } - } - - private void trackRemoved(ShaMapNode child) { - child.walkAnyLeaves(leaf -> deleted.add(leaf.index)); - } - - private void trackAdded(ShaMapNode child) { - child.walkAnyLeaves(leaf -> added.add(leaf.index)); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapInner.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapInner.java deleted file mode 100644 index f7d73dae65..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapInner.java +++ /dev/null @@ -1,331 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.serialized.BytesSink; - -import java.util.Iterator; - -public class ShaMapInner extends ShaMapNode implements Iterable { - public int depth; - int slotBits = 0; - int version = 0; - boolean doCoW; - protected ShaMapNode[] branches = new ShaMapNode[16]; - - public ShaMapInner(int depth) { - this(false, depth, 0); - } - - public ShaMapInner(boolean isCopy, int depth, int version) { - this.doCoW = isCopy; - this.depth = depth; - this.version = version; - } - - protected ShaMapInner copy(int version) { - ShaMapInner copy = makeInnerOfSameClass(depth); - System.arraycopy(branches, 0, copy.branches, 0, branches.length); - copy.slotBits = slotBits; - copy.hash = hash; - copy.version = version; - doCoW = true; - - return copy; - } - - protected ShaMapInner makeInnerOfSameClass(int depth) { - return new ShaMapInner(true, depth, version); - } - - protected ShaMapInner makeInnerChild() { - int childDepth = depth + 1; - if (childDepth >= 64) throw new AssertionError(); - return new ShaMapInner(doCoW, childDepth, version); - } - - // Descend into the tree, find the leaf matching this index - // and if the tree has it. - protected void setLeaf(ShaMapLeaf leaf) { - if (leaf.version == -1) { - leaf.version = version; - } - setBranch(leaf.index, leaf); - } - - private void removeBranch(Hash256 index) { - removeBranch(selectBranch(index)); - } - - public void walkLeaves(LeafWalker leafWalker) { - for (ShaMapNode branch : branches) { - if (branch != null) { - if (branch.isInner()) { - branch.asInner().walkLeaves(leafWalker); - } else if (branch.isLeaf()) { - leafWalker.onLeaf(branch.asLeaf()); - } - } - } - } - - public void walkTree(TreeWalker treeWalker) { - treeWalker.onInner(this); - for (ShaMapNode branch : branches) { - if (branch != null) { - if (branch.isLeaf()) { - ShaMapLeaf ln = branch.asLeaf(); - treeWalker.onLeaf(ln); - } else if (branch.isInner()) { - ShaMapInner childInner = branch.asInner(); - childInner.walkTree(treeWalker); - } - } - } - - } - - public void walkHashedTree(HashedTreeWalker walker) { - walker.onInner(hash(), this); - - for (ShaMapNode branch : branches) { - if (branch != null) { - if (branch.isLeaf()) { - ShaMapLeaf ln = branch.asLeaf(); - walker.onLeaf(branch.hash(), ln); - } else if (branch.isInner()) { - ShaMapInner childInner = branch.asInner(); - childInner.walkHashedTree(walker); - } - } - } - } - - /** - * @return the `only child` leaf or null if other children - */ - public ShaMapLeaf onlyChildLeaf() { - ShaMapLeaf leaf = null; - int leaves = 0; - - for (ShaMapNode branch : branches) { - if (branch != null) { - if (branch.isInner()) { - leaf = null; - break; - } else if (++leaves == 1) { - leaf = branch.asLeaf(); - } else { - leaf = null; - break; - } - } - } - return leaf; - } - - public boolean removeLeaf(Hash256 index) { - PathToIndex path = pathToIndex(index); - if (path.hasMatchedLeaf()) { - ShaMapInner top = path.dirtyOrCopyInners(); - top.removeBranch(index); - path.collapseOnlyLeafChildInners(); - return true; - } else { - return false; - } - } - - public ShaMapItem getItem(Hash256 index) { - ShaMapLeaf leaf = getLeaf(index); - - @SuppressWarnings("unchecked") - ShaMapItem shaMapItem = leaf == null ? null : leaf.item; - return shaMapItem; - } - - public boolean addItem(Hash256 index, ShaMapItem item) { - return addLeaf(new ShaMapLeaf(index, item)); - } - - public boolean updateItem(Hash256 index, ShaMapItem item) { - return updateLeaf(new ShaMapLeaf(index, item)); - } - - public boolean hasLeaf(Hash256 index) { - return pathToIndex(index).hasMatchedLeaf(); - } - - public ShaMapLeaf getLeaf(Hash256 index) { - PathToIndex stack = pathToIndex(index); - if (stack.hasMatchedLeaf()) { - return stack.leaf; - } else { - return null; - } - } - - public boolean addLeaf(ShaMapLeaf leaf) { - PathToIndex stack = pathToIndex(leaf.index); - if (stack.hasMatchedLeaf()) { - return false; - } else { - ShaMapInner top = stack.dirtyOrCopyInners(); - top.addLeafToTerminalInner(leaf); - return true; - } - } - - public boolean updateLeaf(ShaMapLeaf leaf) { - PathToIndex stack = pathToIndex(leaf.index); - if (stack.hasMatchedLeaf()) { - ShaMapInner top = stack.dirtyOrCopyInners(); - // Why not update in place? Because of structural sharing - top.setLeaf(leaf); - return true; - } else { - return false; - } - } - - public PathToIndex pathToIndex(Hash256 index) { - return new PathToIndex(this, index); - } - - /** - * This should only be called on the deepest inners, as it - * does not do any dirtying. - * @param leaf to add to inner - */ - void addLeafToTerminalInner(ShaMapLeaf leaf) { - ShaMapNode branch = getBranch(leaf.index); - if (branch == null) { - setLeaf(leaf); - } else if (branch.isInner()) { - // This should never be called - throw new AssertionError(); - } else if (branch.isLeaf()) { - ShaMapInner inner = makeInnerChild(); - setBranch(leaf.index, inner); - inner.addLeafToTerminalInner(leaf); - inner.addLeafToTerminalInner(branch.asLeaf()); - } - } - - protected void setBranch(Hash256 index, ShaMapNode node) { - setBranch(selectBranch(index), node); - } - - protected ShaMapNode getBranch(Hash256 index) { - return getBranch(index.nibblet(depth)); - } - - public ShaMapNode getBranch(int i) { - return branches[i]; - } - - public ShaMapNode branch(int i) { - return branches[i]; - } - - protected int selectBranch(Hash256 index) { - return index.nibblet(depth); - } - - public boolean hasLeaf(int i) { - return branches[i].isLeaf(); - } - public boolean hasInner(int i) { - return branches[i].isInner(); - } - public boolean hasNone(int i) {return branches[i] == null;} - - private void setBranch(int slot, ShaMapNode node) { - slotBits = slotBits | (1 << slot); - branches[slot] = node; - invalidate(); - } - - private void removeBranch(int slot) { - branches[slot] = null; - slotBits = slotBits & ~(1 << slot); - } - public boolean empty() { - return slotBits == 0; - } - - @Override public boolean isLeaf() { return false; } - @Override public boolean isInner() { return true; } - - @Override - Prefix hashPrefix() { - return HashPrefix.innerNode; - } - - @Override - public void toBytesSink(BytesSink sink) { - for (ShaMapNode branch : branches) { - if (branch != null) { - branch.hash().toBytesSink(sink); - } else { - Hash256.ZERO_256.toBytesSink(sink); - } - } - } - - @Override - public Hash256 hash() { - if (empty()) { - // empty inners have a hash of all ZERO - // it's only valid for a root node to be empty - // any other inner node, must contain at least a - // single leaf - assert depth == 0; - return Hash256.ZERO_256; - } else { - // hash the hashPrefix() and toBytesSink - return super.hash(); - } - } - - public ShaMapLeaf getLeafForUpdating(Hash256 leaf) { - PathToIndex path = pathToIndex(leaf); - if (path.hasMatchedLeaf()) { - return path.invalidatedPossiblyCopiedLeafForUpdating(); - } - return null; - } - - @Override - public Iterator iterator() { - return new Iterator() { - int ix = 0; - - @Override - public boolean hasNext() { - return ix != 16; - } - - @Override - public ShaMapNode next() { - return branch(ix++); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - public int branchCount() { - int populated = 0; - for (ShaMapNode branch : branches) { - if (branch != null) { - populated ++; - } - } - return populated; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapItem.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapItem.java deleted file mode 100644 index a8ec303dbb..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapItem.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.serialized.BytesSink; - -abstract public class ShaMapItem { - abstract void toBytesSink(BytesSink sink); - public abstract ShaMapItem copy(); - public abstract T value(); - public abstract Prefix hashPrefix(); -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapLeaf.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapLeaf.java deleted file mode 100644 index 3a79dacb94..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapLeaf.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.serialized.BytesSink; - -public class ShaMapLeaf extends ShaMapNode { - public Hash256 index; - public ShaMapItem item; - public long version = -1; - - protected ShaMapLeaf(Hash256 index, ShaMapItem item) { - this.index = index; - this.item = item; - } - - @Override public boolean isLeaf() {return true;} - @Override public boolean isInner() {return false;} - - @Override - Prefix hashPrefix() { - return item.hashPrefix(); - } - - @Override - public void toBytesSink(BytesSink sink) { - item.toBytesSink(sink); - index.toBytesSink(sink); - } - - public ShaMapLeaf copy() { - return new ShaMapLeaf(index, item.copy()); - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapNode.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapNode.java deleted file mode 100644 index c5e6d00aca..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/ShaMapNode.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.HalfSha512; -import com.ripple.core.coretypes.hash.Hash256; -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.serialized.BytesSink; - -abstract public class ShaMapNode { - protected Hash256 hash; - - // This saves a lot of instanceof checks - public abstract boolean isLeaf(); - public abstract boolean isInner(); - - public ShaMapLeaf asLeaf() { - return (ShaMapLeaf) this; - } - public ShaMapInner asInner() { - return (ShaMapInner) this; - } - - abstract Prefix hashPrefix(); - abstract public void toBytesSink(BytesSink sink); - - public void invalidate() {hash = null;} - public Hash256 hash() { - if (hash == null) { - hash = createHash(); - } - return hash; - } - private Hash256 createHash() { - HalfSha512 half = HalfSha512.prefixed256(hashPrefix()); - toBytesSink(half); - return half.finish(); - } - /** - * Walk any leaves, possibly this node itself, if it's terminal. - */ - public void walkAnyLeaves(LeafWalker leafWalker) { - if (isLeaf()) { - leafWalker.onLeaf(asLeaf()); - } else { - asInner().walkLeaves(leafWalker); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionResultItem.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionResultItem.java deleted file mode 100644 index 9e4d34a39d..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionResultItem.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.coretypes.hash.prefixes.HashPrefix; -import com.ripple.core.coretypes.hash.prefixes.Prefix; -import com.ripple.core.serialized.BinarySerializer; -import com.ripple.core.serialized.BytesSink; -import com.ripple.core.types.known.tx.result.TransactionResult; - -public class TransactionResultItem extends ShaMapItem { - public TransactionResult result; - - public TransactionResultItem(TransactionResult result) { - this.result = result; - } - - @Override - void toBytesSink(BytesSink sink) { - BinarySerializer write = new BinarySerializer(sink); - write.addLengthEncoded(result.txn); - write.addLengthEncoded(result.meta); - } - - @Override - public ShaMapItem copy() { - // that's ok right ;) these bad boys are immutable anyway - return this; - } - - @Override - public TransactionResult value() { - return result; - } - - @Override - public Prefix hashPrefix() { - return HashPrefix.txNode; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionResultVisitor.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionResultVisitor.java deleted file mode 100644 index 3993b3795d..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionResultVisitor.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.ripple.core.types.shamap; -import com.ripple.core.types.known.tx.result.TransactionResult; - -public interface TransactionResultVisitor { - public void onTransaction(TransactionResult tx); -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionTree.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionTree.java deleted file mode 100644 index 72c4ac4ddc..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/TransactionTree.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.ripple.core.types.shamap; - -import com.ripple.core.types.known.tx.result.TransactionResult; - -import java.util.TreeSet; - -public class TransactionTree extends ShaMap { - public TransactionTree() { - super(); - } - - public TransactionTree(boolean isCopy, int depth) { - super(isCopy, depth); - } - - @Override - protected ShaMapInner makeInnerOfSameClass(int depth) { - return new TransactionTree(true, depth); - } - - public void addTransactionResult(TransactionResult tr) { - TransactionResultItem item = new TransactionResultItem(tr); - addItem(tr.hash, item); - } - - @Override - public TransactionTree copy() { - return (TransactionTree) super.copy(); - } - - public void walkTransactions(final TransactionResultVisitor walker) { - walkLeaves(leaf -> { - TransactionResultItem item = (TransactionResultItem) leaf.item; - walker.onTransaction(item.result); - }); - } - - public TreeSet toTreeSet() { - TreeSet result = new TreeSet<>(); - walkTransactions(result::add); - return result; - } -} diff --git a/ripple-core/src/main/java/com/ripple/core/types/shamap/TreeWalker.java b/ripple-core/src/main/java/com/ripple/core/types/shamap/TreeWalker.java deleted file mode 100644 index de225e9eaf..0000000000 --- a/ripple-core/src/main/java/com/ripple/core/types/shamap/TreeWalker.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.ripple.core.types.shamap; - -public interface TreeWalker { - public void onLeaf(ShaMapLeaf leaf); - public void onInner(ShaMapInner inner); -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/Seed.java b/ripple-core/src/main/java/com/ripple/crypto/Seed.java deleted file mode 100644 index 5c0231828f..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/Seed.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.ripple.crypto; - -import com.ripple.crypto.ecdsa.K256; -import com.ripple.crypto.ed25519.EDKeyPair; -import com.ripple.crypto.keys.IKeyPair; -import com.ripple.encodings.addresses.Addresses; -import com.ripple.encodings.base58.B58; -import com.ripple.utils.Sha512; - -import java.io.UnsupportedEncodingException; -import java.util.Arrays; - -public class Seed { - private final byte[] seedBytes; - private B58.Version version; - - public Seed(byte[] seedBytes) { - this(Addresses.SEED_K256, seedBytes); - } - - public Seed(B58.Version version, byte[] seedBytes) { - this.seedBytes = seedBytes; - this.version = version; - } - - @Override - public String toString() { - return Addresses.encode(seedBytes, version); - } - - public byte[] bytes() { - return seedBytes; - } - - public B58.Version version() { - return version; - } - - public Seed setEd25519() { - this.version = Addresses.SEED_ED25519; - return this; - } - - public IKeyPair keyPair() { - return keyPair(0); - } - - public IKeyPair rootKeyPair() { - return keyPair(-1); - } - - public IKeyPair keyPair(int account) { - if (version == Addresses.SEED_ED25519 || - Arrays.equals(version.bytes, Addresses.SEED_ED25519.bytes)) { - if (account != 0) throw new IllegalStateException(); - return EDKeyPair.from128Seed(seedBytes); - } else { - return K256.createKeyPair(seedBytes, account); - } - - } - - public static Seed fromBase58(String b58) { - B58.Decoded decoded = Addresses.decodeSeed(b58); - return new Seed(decoded.version, decoded.payload); - } - - public static Seed fromPassPhrase(String passPhrase) { - return new Seed(passPhraseToSeedBytes(passPhrase)); - } - - public static byte[] passPhraseToSeedBytes(String phrase) { - try { - return new Sha512(phrase.getBytes("utf-8")).finish128(); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - public static IKeyPair getKeyPair(String b58) { - return fromBase58(b58).keyPair(); - } -} - - diff --git a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/ECDSASignature.java b/ripple-core/src/main/java/com/ripple/crypto/ecdsa/ECDSASignature.java deleted file mode 100644 index ab61bfec30..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/ECDSASignature.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.ripple.crypto.ecdsa; - -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.DERSequenceGenerator; -import org.bouncycastle.asn1.DLSequence; -import org.bouncycastle.crypto.signers.ECDSASigner; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigInteger; - -public class ECDSASignature { - /** The two components of the signature. */ - public BigInteger r, s; - - /** Constructs a signature with the given components. */ - public ECDSASignature(BigInteger r, BigInteger s) { - this.r = r; - this.s = s; - } - - public static boolean isStrictlyCanonical(byte[] sig) { - return checkIsCanonical(sig, true); - } - - public static boolean checkIsCanonical(byte[] sig, boolean strict) { - // Make sure signature is canonical - // To protect against signature morphing attacks - - // Signature should be: - // <30> [ <02> ] [ <02> ] - // where - // 6 <= len <= 70 - // 1 <= lenR <= 33 - // 1 <= lenS <= 33 - - int sigLen = sig.length; - - if ((sigLen < 8) || (sigLen > 72)) - return false; - - if ((sig[0] != 0x30) || (sig[1] != (sigLen - 2))) - return false; - - // Find R and check its length - int rPos = 4, rLen = sig[rPos - 1]; - - if ((rLen < 1) || (rLen > 33) || ((rLen + 7) > sigLen)) - return false; - - // Find S and check its length - int sPos = rLen + 6, sLen = sig[sPos - 1]; - if ((sLen < 1) || (sLen > 33) || ((rLen + sLen + 6) != sigLen)) - return false; - - if ((sig[rPos - 2] != 0x02) || (sig[sPos - 2] != 0x02)) - return false; // R or S have wrong type - - if ((sig[rPos] & 0x80) != 0) - return false; // R is negative - - if ((sig[rPos] == 0) && rLen == 1) - return false; // R is zero - - if ((sig[rPos] == 0) && ((sig[rPos + 1] & 0x80) == 0)) - return false; // R is padded - - if ((sig[sPos] & 0x80) != 0) - return false; // S is negative - - if ((sig[sPos] == 0) && sLen == 1) - return false; // S is zero - - if ((sig[sPos] == 0) && ((sig[sPos + 1] & 0x80) == 0)) - return false; // S is padded - - - byte[] rBytes = new byte[rLen]; - byte[] sBytes = new byte[sLen]; - - System.arraycopy(sig, rPos, rBytes, 0, rLen); - System.arraycopy(sig, sPos, sBytes, 0, sLen); - - BigInteger r = new BigInteger(1, rBytes), s = new BigInteger(1, sBytes); - - BigInteger order = SECP256K1.order(); - - if (r.compareTo(order) != -1 || s.compareTo(order) != -1) { - return false; // R or S greater than modulus - } - if (strict) { - return order.subtract(s).compareTo(s) != -1; - } else { - return true; - } - - } - - public static ECDSASignature createSignature(byte[] hash, ECDSASigner signer) { - BigInteger[] sigs = signer.generateSignature(hash); - BigInteger r = sigs[0], s = sigs[1]; - - BigInteger otherS = SECP256K1.order().subtract(s); - if (s.compareTo(otherS) > 0) { - s = otherS; - } - - return new ECDSASignature(r, s); - } - - /** - * DER is an international standard for serializing data structures which is widely used in cryptography. - * It's somewhat like protocol buffers but less convenient. This method returns a standard DER encoding - * of the signature, as recognized by OpenSSL and other libraries. - */ - public byte[] encodeToDER() { - try { - return derByteStream().toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); // Cannot happen. - } - } - - public static ECDSASignature decodeFromDER(byte[] bytes) { - try { - ASN1InputStream decoder = new ASN1InputStream(bytes); - DLSequence seq = (DLSequence) decoder.readObject(); - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - return null; - } finally { - decoder.close(); - } - // OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be - // Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html - return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - protected ByteArrayOutputStream derByteStream() throws IOException { - // Usually 70-72 bytes. - ByteArrayOutputStream bos = new ByteArrayOutputStream(72); - DERSequenceGenerator seq = new DERSequenceGenerator(bos); - seq.addObject(new ASN1Integer(r)); - seq.addObject(new ASN1Integer(s)); - seq.close(); - return bos; - } -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256.java b/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256.java deleted file mode 100644 index 1f93f23465..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.ripple.crypto.ecdsa; - -import com.ripple.utils.Sha512; -import com.ripple.utils.Utils; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.bouncycastle.math.ec.ECPoint; - -import java.math.BigInteger; - -public class K256 { - - /** - * @param secretKey secret point on the curve as BigInteger - * @return corresponding public point - */ - public static byte[] getPublic(BigInteger secretKey) { - return SECP256K1.basePointMultipliedBy(secretKey); - } - - /** - * @param secretKey secret point on the curve as BigInteger - * @return corresponding public point - */ - private static ECPoint computePublic(BigInteger secretKey) { - return SECP256K1.basePoint().multiply(secretKey); - } - - /** - * @param privateGen secret point on the curve as BigInteger - * @return the corresponding public key is the public generator - * (aka public root key, master public key). - * return as byte[] for convenience. - */ - private static ECPoint computePublicGenerator(BigInteger privateGen) { - return computePublic(privateGen); - } - - private static BigInteger computePrivateGen(byte[] seedBytes) { - return generateKey(seedBytes, null); - } - - public static byte[] computePublicKey(byte[] publicGenBytes, - int accountNumber) { - ECPoint rootPubPoint = SECP256K1.curve().decodePoint(publicGenBytes); - BigInteger scalar = generateKey(publicGenBytes, accountNumber); - ECPoint point = SECP256K1.basePoint().multiply(scalar); - ECPoint offset = rootPubPoint.add(point); - return offset.getEncoded(true); - } - - private static BigInteger computeSecretKey(BigInteger privateGen, - byte[] publicGenBytes, - int accountNumber) { - return generateKey(publicGenBytes, accountNumber) - .add(privateGen).mod(SECP256K1.order()); - } - - /** - * @param seedBytes - a bytes sequence of arbitrary length which will be hashed - * @param discriminator - nullable optional uint32 to hash - * @return a number between [1, order -1] suitable as a private key - */ - private static BigInteger generateKey(byte[] seedBytes, Integer discriminator) { - BigInteger key = null; - for (long i = 0; i <= 0xFFFFFFFFL; i++) { - Sha512 sha512 = new Sha512().add(seedBytes); - if (discriminator != null) { - sha512.addU32(discriminator); - } - sha512.addU32((int) i); - byte[] keyBytes = sha512.finish256(); - key = Utils.uBigInt(keyBytes); - if (key.compareTo(BigInteger.ZERO) > 0 && - key.compareTo(SECP256K1.order()) < 0) { - break; - } - } - return key; - } - - static ECDSASignature createECDSASignature(byte[] hash, ECDSASigner signer) { - return ECDSASignature.createSignature(hash, signer); - } - - public static K256KeyPair createKeyPair(byte[] seedBytes, int accountNumber) { - @SuppressWarnings("SpellCheckingInspection") - BigInteger priv; - BigInteger privateGen; - // The private generator (aka root private key, master private key) - privateGen = computePrivateGen(seedBytes); - ECPoint publicGen = computePublicGenerator(privateGen); - byte[] pubGenBytes = publicGen.getEncoded(true); - - if (accountNumber == -1) { - // The root keyPair - return new K256KeyPair(privateGen, publicGen, pubGenBytes); - } else { - priv = computeSecretKey(privateGen, pubGenBytes, accountNumber); - ECPoint pub = computePublic(priv); - return new K256KeyPair(priv, pub, null); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256KeyPair.java b/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256KeyPair.java deleted file mode 100644 index e417c9d85c..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256KeyPair.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.ripple.crypto.ecdsa; - -import com.ripple.crypto.keys.IKeyPair; -import com.ripple.utils.HashUtils; -import com.ripple.utils.Utils; -import org.bouncycastle.crypto.digests.SHA256Digest; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.bouncycastle.crypto.signers.HMacDSAKCalculator; -import org.bouncycastle.math.ec.ECPoint; - -import java.math.BigInteger; - -public class K256KeyPair extends K256VerifyingKey implements IKeyPair { - private byte[] privateKey; - private ECPrivateKeyParameters privateKeyParameters; - - K256KeyPair(BigInteger privateKey, ECPoint pub, byte[] pubEncoded) { - super(pub, pubEncoded); - this.privateKey = Utils.padTo256(privateKey.toByteArray()); - privateKeyParameters = new ECPrivateKeyParameters(privateKey, - SECP256K1.params()); - } - - @Override - public byte[] privateKey() { - return privateKey; - } - - @Override - public byte[] signMessage(byte[] message) { - byte[] hash = HashUtils.halfSha512(message); - return signHash(hash); - } - - public byte[] signHash(byte[] bytes) { - ECDSASigner signer = newSigner(); - - ECDSASignature sig = K256.createECDSASignature(bytes, signer); - byte[] der = sig.encodeToDER(); - if (!ECDSASignature.isStrictlyCanonical(der)) { - throw new IllegalStateException("Signature is not strictly canonical"); - } - return der; - } - - private ECDSASigner newSigner() { - ECDSASigner signer = new ECDSASigner( - new HMacDSAKCalculator(new SHA256Digest())); - signer.init(true, privateKeyParameters); - return signer; - } - -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256VerifyingKey.java b/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256VerifyingKey.java deleted file mode 100644 index 7681c12ef6..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/K256VerifyingKey.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.ripple.crypto.ecdsa; - -import com.ripple.crypto.keys.IVerifyingKey; -import com.ripple.encodings.addresses.Addresses; -import com.ripple.utils.HashUtils; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.bouncycastle.math.ec.ECPoint; - -public class K256VerifyingKey implements IVerifyingKey { - private final ECPublicKeyParameters keyParameters; - private final byte[] canonicalPublicKey; - - K256VerifyingKey(ECPoint publicKey, byte[] publicKeyBytes) { - if (publicKeyBytes == null) { - publicKeyBytes = publicKey.getEncoded(true); - } else if (publicKey == null) { - publicKey = SECP256K1.curve().decodePoint(publicKeyBytes); - } - canonicalPublicKey = publicKeyBytes; - keyParameters = new ECPublicKeyParameters( - publicKey, SECP256K1.params()); - } - - private K256VerifyingKey(byte[] pubKeyBytes) { - this(null, pubKeyBytes); - } - - @Override - public byte[] canonicalPubBytes() { - return canonicalPublicKey; - } - - @Override - public boolean verify(byte[] message, byte[] signature) { - byte[] bytes = HashUtils.halfSha512(message); - return verifyHash(bytes, signature); - } - - public boolean verifyHash(byte[] hash, byte[] signature) { - ECDSASigner signer = new ECDSASigner(); - signer.init(false, keyParameters); - ECDSASignature sig = ECDSASignature.decodeFromDER(signature); - return sig != null && signer.verifySignature(hash, sig.r, sig.s); - } - - public static K256VerifyingKey fromNodePublicKey(String node) { - byte[] pub = Addresses.decodeNodePublic(node); - return new K256VerifyingKey(pub); - } - - public static K256VerifyingKey fromCanonicalPubBytes(byte[] bytes) { - return new K256VerifyingKey(bytes); - } -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/SECP256K1.java b/ripple-core/src/main/java/com/ripple/crypto/ecdsa/SECP256K1.java deleted file mode 100644 index 5350a27f75..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/ecdsa/SECP256K1.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.ripple.crypto.ecdsa; - -import org.bouncycastle.asn1.sec.SECNamedCurves; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.crypto.params.ECDomainParameters; -import org.bouncycastle.math.ec.ECCurve; -import org.bouncycastle.math.ec.ECPoint; - -import java.math.BigInteger; - -public class SECP256K1 { - private static final ECDomainParameters ecParams; - private static final X9ECParameters params; - - static { - - params = SECNamedCurves.getByName("secp256k1"); - ecParams = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); - } - - public static ECDomainParameters params() { - return ecParams; - } - - public static BigInteger order() { - return ecParams.getN(); - } - - - public static ECCurve curve() { - return ecParams.getCurve(); - } - - public static ECPoint basePoint() { - return ecParams.getG(); - } - - static byte[] basePointMultipliedBy(BigInteger secret) { - return basePoint().multiply(secret).getEncoded(true); - } - -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/ed25519/ED25519.java b/ripple-core/src/main/java/com/ripple/crypto/ed25519/ED25519.java deleted file mode 100644 index 71da658b0c..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/ed25519/ED25519.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ripple.crypto.ed25519; - -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; - -public class ED25519 { - static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable - .getByName("Ed25519"); -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/ed25519/EDKeyPair.java b/ripple-core/src/main/java/com/ripple/crypto/ed25519/EDKeyPair.java deleted file mode 100644 index 5d638617c3..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/ed25519/EDKeyPair.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.ripple.crypto.ed25519; - -import com.ripple.crypto.keys.IKeyPair; -import com.ripple.utils.HashUtils; -import com.ripple.utils.Utils; -import net.i2p.crypto.eddsa.EdDSAEngine; -import net.i2p.crypto.eddsa.EdDSAPrivateKey; -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; - -import java.math.BigInteger; -import java.security.PrivateKey; - -public class EDKeyPair extends EDVerifyingKey implements IKeyPair { - - private final EdDSAPrivateKeySpec keySpec; - - private EDKeyPair(EdDSAPrivateKeySpec keySpec) { - super(new EdDSAPublicKeySpec(keySpec.getA(), ED25519.ed25519), null); - this.keySpec = keySpec; - } - - public static EDKeyPair from256Seed(byte[] seedBytes) { - EdDSAPrivateKeySpec keySpec = new EdDSAPrivateKeySpec(seedBytes, - ED25519.ed25519); - return new EDKeyPair(keySpec); - } - - public static EDKeyPair from128Seed(byte[] seedBytes) { - assert seedBytes.length == 16; - return from256Seed(HashUtils.halfSha512(seedBytes)); - } - - @Override - public byte[] privateKey() { - return keySpec.geta(); - } - - @Override - public byte[] signMessage(byte[] message) { - try { - EdDSAEngine sgr = new EdDSAEngine(sha512digest()); - PrivateKey sKey = new EdDSAPrivateKey(keySpec); - sgr.initSign(sKey); - sgr.update(message); - return sgr.sign(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/ed25519/EDVerifyingKey.java b/ripple-core/src/main/java/com/ripple/crypto/ed25519/EDVerifyingKey.java deleted file mode 100644 index e0c9da8d07..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/ed25519/EDVerifyingKey.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.ripple.crypto.ed25519; - -import com.ripple.crypto.keys.IVerifyingKey; -import net.i2p.crypto.eddsa.EdDSAEngine; -import net.i2p.crypto.eddsa.EdDSAPublicKey; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; -import org.bouncycastle.util.Arrays; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class EDVerifyingKey implements IVerifyingKey { - private final EdDSAPublicKey publicKey; - private final byte[] canonicalPubBytes; - @SuppressWarnings("FieldCanBeLocal") - private final byte[] ED_PREFIX = {(byte) 0xED}; - - EDVerifyingKey(EdDSAPublicKeySpec spec, byte[] pubBytes) { - if (pubBytes == null) { - pubBytes = spec.getA().toByteArray(); - } else if (spec == null) { - spec = new EdDSAPublicKeySpec(pubBytes, ED25519.ed25519); - } - publicKey = new EdDSAPublicKey(spec); - canonicalPubBytes = Arrays.concatenate(ED_PREFIX, pubBytes); - } - - MessageDigest sha512digest() { - try { - return MessageDigest.getInstance("SHA-512"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - @Override - public byte[] canonicalPubBytes() { - return canonicalPubBytes; - } - - @Override - public boolean verify(byte[] message, byte[] sigBytes) { - try { - EdDSAEngine sgr = new EdDSAEngine(sha512digest()); - sgr.initVerify(publicKey); - sgr.update(message); - return sgr.verify(sigBytes); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static EDVerifyingKey fromCanonicalPubBytes(byte[] bytes) { - // Strip the 0xED prefix from the key - byte[] pubBytes = Arrays.copyOfRange(bytes, 1, bytes.length); - return new EDVerifyingKey(null, pubBytes); - } -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/keys/IKeyPair.java b/ripple-core/src/main/java/com/ripple/crypto/keys/IKeyPair.java deleted file mode 100644 index ab76848154..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/keys/IKeyPair.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.ripple.crypto.keys; - -public interface IKeyPair extends IVerifyingKey { - byte[] privateKey(); - byte[] signMessage(byte[] message); -} diff --git a/ripple-core/src/main/java/com/ripple/crypto/keys/IVerifyingKey.java b/ripple-core/src/main/java/com/ripple/crypto/keys/IVerifyingKey.java deleted file mode 100644 index 1b1c6ed7d0..0000000000 --- a/ripple-core/src/main/java/com/ripple/crypto/keys/IVerifyingKey.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.ripple.crypto.keys; - - -import com.ripple.crypto.ecdsa.K256VerifyingKey; -import com.ripple.crypto.ed25519.EDVerifyingKey; -import com.ripple.encodings.common.B16; -import com.ripple.utils.HashUtils; - -public interface IVerifyingKey { - default String canonicalPubHex() { - return B16.encode(canonicalPubBytes()); - } - - default byte[] id() { - return HashUtils.SHA256_RIPEMD160(canonicalPubBytes()); - } - - byte[] canonicalPubBytes(); - - boolean verify(byte[] message, byte[] sigBytes); - - static IVerifyingKey from(byte[] bytes) { - if (bytes[0] == (byte) 0xED) { - return EDVerifyingKey.fromCanonicalPubBytes(bytes); - } else { - return K256VerifyingKey.fromCanonicalPubBytes(bytes); - } - } -} \ No newline at end of file diff --git a/ripple-core/src/main/java/com/ripple/encodings/addresses/Addresses.java b/ripple-core/src/main/java/com/ripple/encodings/addresses/Addresses.java deleted file mode 100644 index 5d8022a9b6..0000000000 --- a/ripple-core/src/main/java/com/ripple/encodings/addresses/Addresses.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.ripple.encodings.addresses; - - -import com.ripple.encodings.base58.B58; -import com.ripple.encodings.basex.EncodingFormatException; -import com.ripple.encodings.basex.IBaseX; -import org.omg.IOP.Encoding; - -public class Addresses { - public static final B58 codec = new B58("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"); - - // Otherwise known as `family seed` - public static final B58.Version SEED_K256 = new B58.Version( - 33, "seedK256", 16); - - public static final B58.Version SEED_ED25519 = new B58.Version( - "01E14B", "seedEd25519", 16); - - public static final B58.Version ACCOUNT_ID = new B58.Version( - 0, "accountId", 20); - - public static final B58.Version NODE_PUBLIC_KEY = new B58.Version( - 28, "nodePublicKey", 33); - - public static final B58.Version NODE_PRIVATE_KEY = new B58.Version( - 33, "nodePrivateKey", 32); - - public static byte[] decode(String encoded, B58.Version version) { - return codec.decodeVersioned(encoded, version).payload; - } - - public static String encode(byte[] bytes, B58.Version version) { - return codec.encodeVersioned(bytes, version); - } - - public static byte[] decodeSeedToBytes(String seed) { - return codec.decodeVersioned(seed, SEED_K256, SEED_ED25519) - .payload; - } - - public static B58.Decoded decodeSeed(String seed) { - return codec.decodeVersioned(seed, SEED_K256, SEED_ED25519); - } - - public static String encodeSeedK256(byte[] bytes) { - return encode(bytes, SEED_K256); - } - - public static String encodeAccountID(byte[] bytes) { - return encode(bytes, ACCOUNT_ID); - } - - public static byte[] decodeAccountID(String id) { - return decode(id, ACCOUNT_ID); - } - - public static String encodeNodePublic(byte[] bytes) { - return encode(bytes, NODE_PUBLIC_KEY); - } - - public static byte[] decodeNodePublic(String base58) { - return decode(base58, NODE_PUBLIC_KEY); - } - - public static boolean isValid(String encoded, B58.Version... versions) { - try { - codec.decodeVersioned(encoded, versions); - return true; - } catch (EncodingFormatException e) { - return false; - } - } - - public static boolean isValidAccountID(String encoded) { - return isValid(encoded, ACCOUNT_ID); - } -} diff --git a/ripple-core/src/main/java/com/ripple/encodings/base58/B58.java b/ripple-core/src/main/java/com/ripple/encodings/base58/B58.java deleted file mode 100644 index a6b928824e..0000000000 --- a/ripple-core/src/main/java/com/ripple/encodings/base58/B58.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ripple.encodings.base58; - -import com.ripple.encodings.basex.BaseX; - -public class B58 extends BaseX { - public B58(String alphabet) { - super(alphabet); - } -} diff --git a/ripple-core/src/main/java/com/ripple/encodings/basex/BaseX.java b/ripple-core/src/main/java/com/ripple/encodings/basex/BaseX.java deleted file mode 100644 index 4ebbfc871a..0000000000 --- a/ripple-core/src/main/java/com/ripple/encodings/basex/BaseX.java +++ /dev/null @@ -1,231 +0,0 @@ -package com.ripple.encodings.basex; - -import com.ripple.utils.HashUtils; - -import java.util.Arrays; - -public class BaseX implements IBaseX { - private char[] alphabet; - private final char encodedZero; - protected final int[] indexes; - - public BaseX(String alphabet_) { - if (alphabet_.length() > 256) { - throw new IllegalArgumentException(); - } - alphabet = alphabet_.toCharArray(); - indexes = new int[128]; - encodedZero = alphabet[0]; - Arrays.fill(indexes, -1); - for (int i = 0; i < alphabet.length; i++) { - indexes[alphabet[i]] = i; - } - } - - private static String repeat(int times, char repeated) { - char[] chars = new char[times]; - Arrays.fill(chars, repeated); - return new String(chars); - } - - /** - * Encodes the given bytes as a base58 string (no checksum is appended). - * - * @param input the bytes to encode - * @return the base58-encoded string - */ - @Override - public String encode(byte[] input) { - if (input.length == 0) { - return ""; - } - // Count leading zeros. - int zeros = 0; - while (zeros < input.length && input[zeros] == 0) { - ++zeros; - } - // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) - input = Arrays.copyOf(input, input.length); // since we modify it in-place - char[] encoded = new char[input.length * 2]; // upper bound - int outputStart = encoded.length; - for (int inputStart = zeros; inputStart < input.length; ) { - encoded[--outputStart] = alphabet[divmod(input, inputStart, 256, 58)]; - if (input[inputStart] == 0) { - ++inputStart; // optimization - skip leading zeros - } - } - // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input. - while (outputStart < encoded.length && encoded[outputStart] == encodedZero) { - ++outputStart; - } - while (--zeros >= 0) { - encoded[--outputStart] = encodedZero; - } - // Return encoded string (including encoded leading zeros). - return new String(encoded, outputStart, encoded.length - outputStart); - } - - /** - * Decodes the given base58 string into the original data bytes. - * - * @param input the base58-encoded string to decode - * @return the decoded data bytes - * @throws EncodingFormatException if the given string is not a valid base58 string - */ - @Override - public byte[] decode(String input) throws EncodingFormatException { - if (input.length() == 0) { - return new byte[0]; - } - // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). - byte[] input58 = new byte[input.length()]; - for (int i = 0; i < input.length(); ++i) { - char c = input.charAt(i); - int digit = c < 128 ? indexes[c] : -1; - if (digit < 0) { - throw new EncodingFormatException("Illegal character " + c + " at position " + i); - } - input58[i] = (byte) digit; - } - // Count leading zeros. - int zeros = 0; - while (zeros < input58.length && input58[zeros] == 0) { - ++zeros; - } - // Convert base-58 digits to base-256 digits. - byte[] decoded = new byte[input.length()]; - int outputStart = decoded.length; - for (int inputStart = zeros; inputStart < input58.length; ) { - decoded[--outputStart] = divmod(input58, inputStart, 58, 256); - if (input58[inputStart] == 0) { - ++inputStart; // optimization - skip leading zeros - } - } - // Ignore extra leading zeroes that were added during the calculation. - while (outputStart < decoded.length && decoded[outputStart] == 0) { - ++outputStart; - } - // Return decoded data (including original number of leading zeros). - return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); - } - - /** - * Decodes the given base58 string into the original data bytes, using the checksum in the - * last 4 bytes of the decoded data to verify that the rest are correct. The checksum is - * removed from the returned data. - * - * @param input the base58-encoded string to decode (which should include the checksum) - * @throws EncodingFormatException if the input is not base 58 or the checksum does not validate. - */ - public byte[] decodeChecked(String input) throws EncodingFormatException { - byte[] decoded = decode(input); - if (decoded.length < 4) - throw new EncodingFormatException("Input too short"); - byte[] data = Arrays.copyOfRange(decoded, 0, decoded.length - 4); - byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length); - byte[] actualChecksum = Arrays.copyOfRange(HashUtils.doubleDigest(data), 0, 4); - if (!Arrays.equals(checksum, actualChecksum)) - throw new EncodingFormatException("Checksum does not validate"); - return data; - } - - /** - * Divides a number, represented as an array of bytes each containing a single digit - * in the specified base, by the given divisor. The given number is modified in-place - * to contain the quotient, and the return value is the remainder. - * - * @param number the number to divide - * @param firstDigit the index within the array of the first non-zero digit - * (this is used for optimization by skipping the leading zeros) - * @param base the base in which the number's digits are represented (up to 256) - * @param divisor the number to divide by (up to 256) - * @return the remainder of the division operation - */ - private byte divmod(byte[] number, int firstDigit, int base, int divisor) { - // this is just long division which accounts for the base of the input digits - int remainder = 0; - for (int i = firstDigit; i < number.length; i++) { - int digit = (int) number[i] & 0xFF; - int temp = remainder * base + digit; - number[i] = (byte) (temp / divisor); - remainder = temp % divisor; - } - return (byte) remainder; - } - - @Override - public byte[] findPrefix(int payLoadLength, String desiredPrefix) { - if (alphabet.length != 58) { - throw new IllegalStateException("Must be base58"); - } - int totalLength = payLoadLength + 4; // for the checksum - double chars = Math.log(Math.pow(256, totalLength)) / Math.log(58); - int requiredChars = (int) Math.ceil(chars + 0.2D); - // Mess with this to see stability tests fail - int charPos = (alphabet.length / 2) - 1; - char padding = alphabet[(charPos)]; - String template = desiredPrefix + repeat(requiredChars, padding); - byte[] decoded = decode(template); - return copyOfRange(decoded, 0, decoded.length - totalLength); - } - - @Override - public String encodeVersioned(byte[] input, Version version) { - if (input.length != version.expectedLength) { - throw new IllegalArgumentException( - "input length=" + input.length + - ", expected=" + version.expectedLength); - } - return encode(concatVersionAndAddChecksum(input, version.bytes)); - } - - private byte[] concatVersionAndAddChecksum(byte[] input, byte[] version) { - byte[] buffer = new byte[input.length + version.length]; - System.arraycopy(version, 0, buffer, 0, version.length); - System.arraycopy(input, 0, buffer, version.length, input.length); - byte[] checkSum = copyOfRange(HashUtils.doubleDigest(buffer), 0, 4); - byte[] output = new byte[buffer.length + checkSum.length]; - System.arraycopy(buffer, 0, output, 0, buffer.length); - System.arraycopy(checkSum, 0, output, buffer.length, checkSum.length); - return output; - } - - public Decoded decodeVersioned(String input, - Version... possibleVersions) throws EncodingFormatException { - - byte[] buffer = decodeChecked(input); - - Version foundVersion = null; - int expectedLength = possibleVersions[0].expectedLength; - int versionLength = buffer.length - expectedLength; - byte[] versionBytes = copyOfRange(buffer, 0, versionLength); - - for (Version possible : possibleVersions) { - if (possible.expectedLength != expectedLength) { - throw new IllegalStateException(); - } - - if (Arrays.equals(possible.bytes, versionBytes)) { - foundVersion = possible; - break; - } - } - if (foundVersion == null) { - throw new EncodingFormatException("Incorrect version"); - } - - byte[] bytes = copyOfRange(buffer, versionLength, buffer.length); - if (bytes.length != expectedLength) { - throw new EncodingFormatException("Incorrect length"); - } - - return new Decoded(foundVersion, bytes); - } - - private byte[] copyOfRange(byte[] source, int from, int to) { - byte[] range = new byte[to - from]; - System.arraycopy(source, from, range, 0, range.length); - return range; - } - -} \ No newline at end of file diff --git a/ripple-core/src/main/java/com/ripple/encodings/basex/EncodingFormatException.java b/ripple-core/src/main/java/com/ripple/encodings/basex/EncodingFormatException.java deleted file mode 100644 index 4c56ffa1e9..0000000000 --- a/ripple-core/src/main/java/com/ripple/encodings/basex/EncodingFormatException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.ripple.encodings.basex; - -public class EncodingFormatException extends RuntimeException{ - public EncodingFormatException(String message) { - super(message); - } -} diff --git a/ripple-core/src/main/java/com/ripple/encodings/basex/IBaseX.java b/ripple-core/src/main/java/com/ripple/encodings/basex/IBaseX.java deleted file mode 100644 index 84d98aa51b..0000000000 --- a/ripple-core/src/main/java/com/ripple/encodings/basex/IBaseX.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.ripple.encodings.basex; - -import com.ripple.encodings.common.B16; - -public interface IBaseX { - String encode(byte[] input); - byte[] decode(String input); - - byte[] findPrefix(int payLoadLength, String desiredPrefix); - - String encodeVersioned(byte[] input, Version version); - Decoded decodeVersioned(String input, Version... possibleVersions); - - class Decoded { - public final Version version; - public final byte[] payload; - - Decoded(Version version, byte[] payload) { - this.version = version; - this.payload = payload; - } - } - - class Version { - public final byte[] bytes; - public final String name; - public final int expectedLength; - - public Version(byte[] bytes, String name, int length) { - this.bytes = bytes; - this.name = name; - this.expectedLength = length; - } - public Version(int aByte, String name, int length) { - this(new byte[]{(byte) aByte}, name, length); - } - public Version(String hex, String name, int length) { - this(B16.decode(hex), name, length); - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/encodings/common/B16.java b/ripple-core/src/main/java/com/ripple/encodings/common/B16.java deleted file mode 100644 index 9ab895721c..0000000000 --- a/ripple-core/src/main/java/com/ripple/encodings/common/B16.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ripple.encodings.common; - - -import static org.bouncycastle.util.encoders.Hex.toHexString; - -public class B16 { - public static String toStringTrimmed(byte[] bytes) { - int offset = 0; - if (bytes[0] == 0) { - offset = 1; - } - return toHexString(bytes, offset, bytes.length - offset).toUpperCase(); - } - public static String encode(byte[] bytes) { - return toHexString(bytes).toUpperCase(); - } - public static byte[] decode(String hex) { - return org.bouncycastle.util.encoders.Hex.decode(hex); - } -} diff --git a/ripple-core/src/main/java/com/ripple/encodings/common/B64.java b/ripple-core/src/main/java/com/ripple/encodings/common/B64.java deleted file mode 100644 index e48b7d1a70..0000000000 --- a/ripple-core/src/main/java/com/ripple/encodings/common/B64.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ripple.encodings.common; - -import org.bouncycastle.util.encoders.Base64; - -public class B64 { - public static String toString(byte[] bytes) { - return Base64.toBase64String(bytes); - } - public static byte[] decode(String string) { - return Base64.decode(string); - } -} diff --git a/ripple-core/src/main/java/com/ripple/utils/HashUtils.java b/ripple-core/src/main/java/com/ripple/utils/HashUtils.java deleted file mode 100644 index 2d79986957..0000000000 --- a/ripple-core/src/main/java/com/ripple/utils/HashUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.ripple.utils; - -import org.bouncycastle.crypto.digests.RIPEMD160Digest; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class HashUtils { - private static final MessageDigest digest; - static { - try { - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); // Can't happen. - } - } - - /** - * See {@link HashUtils#doubleDigest(byte[], int, int)}. - */ - public static byte[] doubleDigest(byte[] input) { - return doubleDigest(input, 0, input.length); - } - - /** - * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. This is - * standard procedure in Bitcoin. The resulting hash is in big endian form. - */ - public static byte[] doubleDigest(byte[] input, int offset, int length) { - synchronized (digest) { - digest.reset(); - digest.update(input, offset, length); - byte[] first = digest.digest(); - return digest.digest(first); - } - } - - public static byte[] halfSha512(byte[] bytes) { - return new Sha512(bytes).finish256(); - } - - public static byte[] quarterSha512(byte[] bytes) { - return new Sha512(bytes).finish128(); - } - - public static byte[] sha512(byte[] bytes) { - return new Sha512(bytes).finish(); - } - - public static byte[] SHA256_RIPEMD160(byte[] input) { - try { - byte[] sha256 = MessageDigest.getInstance("SHA-256").digest(input); - RIPEMD160Digest digest = new RIPEMD160Digest(); - digest.update(sha256, 0, sha256.length); - byte[] out = new byte[20]; - digest.doFinal(out, 0); - return out; - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); // Cannot happen. - } - } -} diff --git a/ripple-core/src/main/java/com/ripple/utils/Sha512.java b/ripple-core/src/main/java/com/ripple/utils/Sha512.java deleted file mode 100644 index bafd20b134..0000000000 --- a/ripple-core/src/main/java/com/ripple/utils/Sha512.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.ripple.utils; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; - -public class Sha512 { - private MessageDigest messageDigest; - - public Sha512() { - try { - messageDigest = MessageDigest.getInstance("SHA-512"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - public Sha512(byte[] start) { - this(); - add(start); - } - - public Sha512 add(byte[] bytes) { - messageDigest.update(bytes); - return this; - } - - public Sha512 addU32(int i) { - messageDigest.update((byte) ((i >>> 24) & 0xFF)); - messageDigest.update((byte) ((i >>> 16) & 0xFF)); - messageDigest.update((byte) ((i >>> 8) & 0xFF)); - messageDigest.update((byte) ((i) & 0xFF)); - return this; - } - - private byte[] finishTaking(int size) { - byte[] hash = new byte[size]; - System.arraycopy(messageDigest.digest(), 0, hash, 0, size); - return hash; - } - - public byte[] finish128() { - return finishTaking(16); - } - - public byte[] finish256() { - return finishTaking(32); - } - - public byte[] finish() { - return messageDigest.digest(); - } -} diff --git a/ripple-core/src/main/java/com/ripple/utils/Utils.java b/ripple-core/src/main/java/com/ripple/utils/Utils.java deleted file mode 100644 index 8376f1637f..0000000000 --- a/ripple-core/src/main/java/com/ripple/utils/Utils.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.ripple.utils; - -import com.ripple.encodings.common.B16; - -import java.math.BigInteger; -import java.util.Arrays; - -public class Utils { - public static String bigHex(BigInteger bn) { - return B16.toStringTrimmed(bn.toByteArray()); - } - - public static BigInteger uBigInt(byte[] bytes) { - return new BigInteger(1, bytes); - } - - public static byte[] leadingZeroesTrimmedOrPaddedTo(int size, byte[] bytes) { - if (bytes.length == size) { - return bytes; - } - if (bytes.length > size) { - int length = bytes.length; - int offset = 0; - while (length > size && bytes[offset++] == 0) { - length--; - } - if (length > size) { - throw new IllegalArgumentException( - "bytes.length: " + bytes.length + " > " + - size); - } - return copyFrom(bytes, offset, size); - } - - return padTo(bytes, size); - } - - public static byte[] padTo256(byte[] bytes) { - return leadingZeroesTrimmedOrPaddedTo(32, bytes); - } - - public static byte[] padTo160(byte[] bytes) { - return leadingZeroesTrimmedOrPaddedTo(20, bytes); - } - - private static byte[] copyFrom(byte[] bytes, int offset, int size) { - byte[] result = new byte[size]; - System.arraycopy(bytes, offset, result, 0, size); - return result; - } - - private static byte[] padTo(byte[] bytes, int size) { - byte[] result = new byte[size]; - int offset = result.length - bytes.length; - System.arraycopy(bytes, 0, result, offset, bytes.length); - return result; - } -} diff --git a/settings.gradle b/settings.gradle index 7c7add5bef..27d8397e5d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':card-android', ':server-android', ':card-common', ':ripple-core' +include ':app', ':card-android', ':server-android', ':card-common', ':ripple-core-0.0.1'