Skip to content

sublimator/ripple-lib-java-sucks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 

Repository files navigation

#ripple-lib-java sucks

In ripple, you'll note that there's a serialization format where there is an enumeration of fields, each with a unique symbolic string representation (UpperCase by convention), and also with a type, and nth-of-type ordinal.

See here

    SField const sfIndexNext     = make::one(&sfIndexNext,     STI_UINT64, 1, "IndexNext");
    SField const sfIndexPrevious = make::one(&sfIndexPrevious, STI_UINT64, 2, "IndexPrevious");
    SField const sfBookNode      = make::one(&sfBookNode,      STI_UINT64, 3, "BookNode");

Serialized types include object types and array types, such that you can make arbitrarily nested data structures.

The objects are serialized as so:

$field_bytes$serialized_type_bytes...

This is roughly equivalent to:

    {"$key" : "$value", ...}

Thus in some senses it's much like json, as the structure is somewhat self describing. (In contrast to a way of encoding structures where the $keys were implied by position in a tuple of values)

The type of the $value when serialized, implies the length, for example a STI_UINT64 is obviously 8 bytes.

We've established that the encoding is similar to JSON, in that it encodes arbitary structures. It's just that the keys are all predefined.

Now, consider the type ordinal enumeration:

// // types (common)
STI_UINT16 = 1,
STI_UINT32 = 2,
STI_UINT64 = 3,
STI_HASH128 = 4,
STI_HASH256 = 5,
STI_AMOUNT = 6,
STI_VL = 7,
STI_ACCOUNT = 8,
// 9-13 are reserved
STI_OBJECT = 14,
STI_ARRAY = 15,

// types (uncommon)
STI_UINT8 = 16,
STI_HASH160 = 17,
STI_PATHSET = 18,
STI_VECTOR256 = 19,

And consider some of the fields:

// 16-bit integers
SField const sfLedgerEntryType = make::one(&sfLedgerEntryType, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never);
SField const sfTransactionType = make::one(&sfTransactionType, STI_UINT16, 2, "TransactionType");

While being 16 bit integers, both fields actually have richer semantics. They are in fact both enums for various transaction types.

  1. TransactionType
  2. LedgerEntryType

The string symbolic representations for TransactionType are defined [here] (https://github.com/ripple/rippled/blob/1a7eafb6993f95c4d34e00669a70c8dd4ec0c9ba/src/ripple/module/data/protocol/TxFormats.cpp#L56) and used here.

Now look at some of the STI_UINT32 fields:

// 32-bit integers (common)
SField const sfFlags             = make::one(&sfFlags,             STI_UINT32,  2, "Flags");
SField const sfLedgerSequence    = make::one(&sfLedgerSequence,    STI_UINT32,  6, "LedgerSequence");
...
SField const sfExpiration        = make::one(&sfExpiration,        STI_UINT32, 10, "Expiration");

LedgerSequence is just a standard number, mapping naturally to std::uint32_t in C++ or a long in Java (which has no unsigned integer)

Flags is a little more interesting; it packs in a bunch of per TransactionType boolean options into an erstwhile 32 bit unsigned int. These options have [symbolic names](https://github.com/ripple/ripple d/blob/1a7eafb6993f95c4d34e00669a70c8dd4ec0c9ba/src/ripple/module/data/protocol/TxFlags.h#L70):

// OfferCreate flags:
tfPassive
tfImmediateOrCancel
tfFillOrKill
tfSell

Finally, Expiration is an integer, meaning seconds elapsed since 1 Jan 2000 00:00:00 GMT, and thus a timestamp.

Imagine a Java class with each of these fields:

class HodgePodge {
    long flags;
    long close_time;
    long ledger_index;
}

It would't lead to the nicest API. Arguably, close_time would better be represented as some kind of Date/LocalDateTime instance, and even the flags could have a nicer API if a richer type was used.

With all that in mind, consider the current implementation of ripple types in ripple-lib-java. For each of the STI_* enumerated above there's a class implementing SerializedType:

public interface SerializedType {
    Object toJSON();
    byte[] toBytes();
    String toHex();
    void toBytesSink(BytesSink to);
}

In ripple-lib-java all the fields are represented by a Field enum class. The object classes (used to represent a transaction) are essentially wrappers around Map<Field, SerializedType> So, again, this matches the json like ability to create arbitrary structures, except limited to predefined keys.

Like most JSON apis, the typical api was tried:

    object.getAmount(Field.Amount)

However, what about type safety for put?:

    object.putAmount(Field.LedgerSequence, anAmount)

For this reason, typed fields were enumerated and placed as static members on classes:

    object.get(Amount.LimitAmount)
    object.put(UInt32.LedgerSequence, anAmount) <--- a compile time error

Built on top of these objects (STObject) is a hierarchy for transactions:

STObject
    Transaction
        Payment
        OfferCreate

The Transaction class has accessors for common transaction fields, and various methods:

public class Transaction extends STObject {
...
    public TransactionType transactionType() {
        return (TransactionType) get(Field.TransactionType);
    }

    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 VariableLength signingPubKey() {return get(VariableLength.SigningPubKey);}
    public VariableLength txnSignature() {return get(VariableLength.TxnSignature);}
    public AccountID account() {return get(AccountID.Account);}
    public void transactionType(TransactionType 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(VariableLength val) {put(Field.SigningPubKey, val);}
    public void txnSignature(VariableLength val) {put(Field.TxnSignature, val);}
    public void account(AccountID val) {put(Field.Account, val);}
...

While this does lead to nice auto completion, and discoverability of fields, and is an improvement over using the raw STObject api everywhere, might there not be a nicer API using POJOS and reflection rather than building objects around a Map<Field, SerializedType> ?

Other than the TransactionType accessors, which is one of the few SerializedType implementations that doesn't have a corresponding STI_TRANSACTION_TYPE, most of the types used are generic. Note that flags() just returns a UInt32, the standard implementation, with nothing particularly nice about it.

Exhibit le suckery:

public void setCanonicalSignatureFlag() {
    UInt32 flags = get(UInt32.Flags);
    if (flags == null) {
        flags = CANONICAL_SIGNATURE;
    } else {
        flags = flags.or(CANONICAL_SIGNATURE);
    }
    put(UInt32.Flags, flags);
}

Perhaps better would be classes like this:

class Payment extends Transaction {
    public Amount amount;
    public AccountID destination;
    public Optional<PathSet> paths;
    public Optional<SendMax> sendMax;
}

Currently, classes like Payment transitively depend on STObject for json/binary serialization but that's not necessarily the only way to do it.

Perhaps much in the same way the Jackson API can do:

    mapper.readValue(jsonNode, Payment.class)

We could build something that likewise used reflection:

    mapper.readValue(rawSTObjectInstance, Payment.class)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published