package com.ripple.core.types.known.tx.result; 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.tx.Transaction; import com.ripple.encodings.common.B16; import org.json.JSONObject; import java.util.HashMap; import java.util.Map; public class TransactionResult implements Comparable<TransactionResult>{ // 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, consider just killing this field, as not all have them @Deprecated public Hash256 ledgerHash; // TODO, in practice this class is only for validated results so ... @Deprecated 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; // This is deprecated because it's not always set. @Deprecated public JSONObject message; public boolean isPayment() { return transactionType() == TransactionType.Payment; } 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<AccountID, STObject> modifiedRoots() { HashMap<AccountID, STObject> accounts = null; if (meta.has(Field.AffectedNodes)) { accounts = new HashMap<AccountID, STObject>(); STArray affected = meta.get(STArray.AffectedNodes); for (STObject node : affected) { if (node.has(Field.ModifiedNode)) { node = node.get(STObject.ModifiedNode); if (STObject.ledgerEntryType(node) == LedgerEntryType.AccountRoot) { STObject finalFields = node.get(STObject.FinalFields); AccountID key; if (finalFields != null) { key = finalFields.get(AccountID.Account); accounts.put(key, node); } } } } } 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(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 RuntimeException("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.translate.fromHex(json.getString(key)); } else { JSONObject tx_json = json.getJSONObject(key); return STObject.translate.fromJSONObject(tx_json); } } public TransactionResult(JSONObject json, Source resultMessageSource) { message = json; if (resultMessageSource == Source.transaction_subscription_notification) { engineResult = EngineResult.valueOf(json.getString("engine_result")); validated = json.getBoolean("validated"); ledgerHash = Hash256.translate.fromString(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.translate.fromJSONObject(json.getJSONObject("metaData")); txn = (Transaction) STObject.translate.fromJSONObject(json); hash = txn.get(Hash256.hash); engineResult = meta.engineResult(); ledgerIndex = new UInt32(json.getLong("ledger_index")); ledgerHash = null; } 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); ledgerHash = null; // XXXXXX 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")); ledgerHash = null; } } 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.translate.fromHex(json.getString("meta")); this.txn = (Transaction) STObject.translate.fromBytes(decodedTx); if (account_tx) { hash = Index.transactionID(decodedTx); } else { hash = Hash256.translate.fromHex(json.getString("hash")); } this.txn.put(Field.hash, hash); engineResult = meta.engineResult(); ledgerIndex = new UInt32(json.getLong("ledger_index")); ledgerHash = null; } } } @Override public String toString() { // message is deprecated if (message != null) { return message.toString(); } else { 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.translate.fromBytes(meta.toBytes()); Transaction txnCopy = (Transaction) STObject.translate.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; } }