Updated on 2026-08-14

This commit is contained in:
Tangem 2019-03-18 17:58:56 +03:00
parent ecc6243780
commit 241b29c5df
170 changed files with 13844 additions and 6 deletions

1
.idea/gradle.xml generated
View file

@ -11,6 +11,7 @@
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/card-android" />
<option value="$PROJECT_DIR$/card-common" />
<option value="$PROJECT_DIR$/ripple-core" />
<option value="$PROJECT_DIR$/server-android" />
</set>
</option>

View file

@ -44,6 +44,7 @@ android {
}
dependencies {
implementation project(':ripple-core')
implementation project(':card-common')
implementation project(':card-android')
implementation project(':server-android')

View file

@ -12,12 +12,13 @@ public enum Blockchain {
Ethereum("ETH", "ETH", 1.0, R.drawable.ic_logo_ethereum, "Ethereum"),
EthereumTestNet("ETH/test", "ETH", 1.0, R.drawable.ic_logo_ethereum_testnet, "Ethereum Testnet"),
Token("Token", "ETH", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"),
NftToken("NftToken", "", 1.0, R.drawable.ic_logo_bat_token, "Ethereum"),
NftToken("NftToken", "", 1.0, R.drawable.tangem2, "Ethereum"),
BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"),
Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Litecoin"),
Rootstock("RSK", "RBTC", 1.0, R.drawable.ic_logo_bitcoin, "Rootstock"),
RootstockToken("Token", "RBTC", 1.0, R.drawable.ic_logo_bat_token, "Rootstock"),
Cardano("CARDANO", "ADA", 1000000.0,R.drawable.ic_logo_bitcoin, "Cardano");
Litecoin("LTC", "LTC", 100000000.0, R.drawable.tangem2, "Litecoin"),
Rootstock("RSK", "RBTC", 1.0, R.drawable.tangem2, "Rootstock"),
RootstockToken("Token", "RBTC", 1.0, R.drawable.tangem2, "Rootstock"),
Cardano("CARDANO", "ADA", 1000000.0, R.drawable.tangem2, "Cardano"),
Ripple ("XRP", "XRP", 1000000.0, R.drawable.tangem2, "Ripple");
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
mID = ID;
@ -92,6 +93,9 @@ public enum Blockchain {
case "BTC":
return R.drawable.ic_logo_bitcoin;
case "BCH":
return R.drawable.ic_logo_bitcoin_cash;
case "Token":
if (symbolName.equals("SEED"))
return R.drawable.ic_logo_seed;

View file

@ -0,0 +1,15 @@
package com.tangem.data.network;
import com.tangem.data.network.model.RippleBody;
import com.tangem.data.network.model.RippleResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Headers;
import retrofit2.http.POST;
public interface RippleApi {
@Headers("Content-Type: application/json")
@POST
Call<RippleResponse> ripple(@Body RippleBody body);
}

View file

@ -0,0 +1,114 @@
package com.tangem.data.network;
import android.util.Log;
import com.tangem.App;
import com.tangem.data.network.model.RippleBody;
import com.tangem.data.network.model.RippleResponse;
import java.util.HashMap;
import java.util.Map;
import androidx.annotation.NonNull;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class ServerApiRipple {
private static String TAG = ServerApiRipple.class.getSimpleName();
public static final String RIPPLE_ACCOUNT_INFO = "account_info";
public static final String RIPPLE_ACCOUNT_UNCONFIRMED = "account_unconfirmed";
public static final String RIPPLE_SUBMIT = "submit";
public static final String RIPPLE_FEE = "fee";
private int requestsCount=0;
public static String lastNode;
public boolean isRequestsSequenceCompleted() {
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
return requestsCount <= 0;
}
private ResponseListener responseListener;
public interface ResponseListener {
void onSuccess(String method, RippleResponse infuraResponse);
void onFail(String method, String message);
}
public void setResponseListener(ResponseListener listener) {
responseListener = listener;
}
public void requestData(String method, int id, String wallet, String tx) {
requestsCount++;
String rippleURL = "http://s1.ripple.com:51234/"; //TODO: make random selection
lastNode = rippleURL; //TODO: show node instead of URL
Retrofit retrofitRipple = new Retrofit.Builder()
.baseUrl(rippleURL)
.addConverterFactory(GsonConverterFactory.create())
.build();
RippleApi rippleApi = retrofitRipple.create(RippleApi.class);
RippleBody rippleBody;
HashMap<String, String> paramsMap;
switch (method) {
case RIPPLE_ACCOUNT_INFO:
paramsMap = new HashMap<>();
paramsMap.put("account", wallet);
paramsMap.put("ledger_index", "validated");
rippleBody = new RippleBody(method, paramsMap);
break;
case RIPPLE_ACCOUNT_UNCONFIRMED:
paramsMap = new HashMap<>();
paramsMap.put("account", wallet);
paramsMap.put("ledger_index", "current");
// paramsMap.put("queue", "true"); TODO: make queue check if needed
rippleBody = new RippleBody(RIPPLE_ACCOUNT_INFO, paramsMap);
break;
case RIPPLE_FEE:
rippleBody = new RippleBody(method, new HashMap<>());
break;
case RIPPLE_SUBMIT:
paramsMap = new HashMap<>();
paramsMap.put("tx_blob", tx);
rippleBody = new RippleBody(method, paramsMap);
break;
default:
rippleBody = new RippleBody();
}
Call<RippleResponse> call = rippleApi.ripple(rippleBody);
call.enqueue(new Callback<RippleResponse>() {
@Override
public void onResponse(@NonNull Call<RippleResponse> call, @NonNull Response<RippleResponse> response) {
if (response.code() == 200) {
requestsCount--;
responseListener.onSuccess(method, response.body());
Log.i(TAG, "requestData " + method + " onResponse " + response.code());
} else {
responseListener.onFail(method, String.valueOf(response.code()));
Log.e(TAG, "requestData " + method + " onResponse " + response.code());
}
}
@Override
public void onFailure(@NonNull Call<RippleResponse> call, @NonNull Throwable t) {
responseListener.onFail(method, String.valueOf(t.getMessage()));
Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage());
}
});
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.data.network.model;
import java.util.ArrayList;
import java.util.HashMap;
public class RippleBody {
private String method;
private ArrayList<HashMap<String,String>> params;
public RippleBody() {
}
//for RIPPLE_FEE
public RippleBody(String method) {
this.method = method;
}
public RippleBody(String method, HashMap<String,String> paramsMap) {
this.method = method;
ArrayList<HashMap<String, String>> paramsList = new ArrayList<>();
paramsList.add(paramsMap);
this.params = paramsList;
}
}

View file

@ -0,0 +1,52 @@
package com.tangem.data.network.model
import com.google.gson.annotations.SerializedName
data class RippleResponse(
@SerializedName("result")
var result: RippleResult? = null
)
data class RippleResult(
@SerializedName("account_data")
var account_data: RippleAccountData? = null,
@SerializedName("validated")
var validated: Boolean? = null,
//for RIPPLE_FEE
@SerializedName("drops")
var drops: FeeDrops? = null,
//for RIPPLE_SUBMIT
@SerializedName("engine_result")
var engine_result: String? = null,
//for RIPPLE_SUBMIT
@SerializedName ("engine_result_message")
var engine_result_message: String? = null
)
data class RippleAccountData(
@SerializedName("Account")
var account: String? = null,
@SerializedName("Balance")
var balance: String? = null,
@SerializedName("Sequence")
var sequence: Int? = null
)
data class FeeDrops(
//enough to put tx to queue
@SerializedName("minimum_fee")
var minimum_fee: String? = null,
//enough to put tx to current ledger
@SerializedName("open_ledger_fee")
var open_ledger_fee: String? = null,
@SerializedName("median_fee")
var median_fee: String? = null
)

View file

@ -0,0 +1,115 @@
package com.tangem.domain.wallet.xrp;
import java.math.BigInteger;
/**
* Created by Ilia on 15.02.2018.
*/
public class XrpBase58 {
private static final char[] BASE58 = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz".toCharArray();
private static final int BASE58_CHUNK_DIGITS = 10;//how many base 58 digits fits in long
private static final BigInteger BASE58_CHUNK_MOD = BigInteger.valueOf(0x5fa8624c7fba400L); //58^BASE58_CHUNK_DIGITS
private static final byte[] BASE58_VALUES = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1,
-1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1,
-1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
public static byte[] decodeBase58(String input) {
if (input == null) {
return null;
}
input = input.trim();
if (input.length() == 0) {
return new byte[0];
}
BigInteger resultNum = BigInteger.ZERO;
int nLeadingZeros = 0;
while (nLeadingZeros < input.length() && input.charAt(nLeadingZeros) == BASE58[0]) {
nLeadingZeros++;
}
long acc = 0;
int nDigits = 0;
int p = nLeadingZeros;
while (p < input.length()) {
int v = BASE58_VALUES[input.charAt(p) & 0xff];
if (v >= 0) {
acc *= 58;
acc += v;
nDigits++;
if (nDigits == BASE58_CHUNK_DIGITS) {
resultNum = resultNum.multiply(BASE58_CHUNK_MOD).add(BigInteger.valueOf(acc));
acc = 0;
nDigits = 0;
}
p++;
} else {
break;
}
}
if (nDigits > 0) {
long mul = 58;
while (--nDigits > 0) {
mul *= 58;
}
resultNum = resultNum.multiply(BigInteger.valueOf(mul)).add(BigInteger.valueOf(acc));
}
final int BASE58_SPACE = -2;
while (p < input.length() && BASE58_VALUES[input.charAt(p) & 0xff] == BASE58_SPACE) {
p++;
}
if (p < input.length()) {
return null;
}
byte[] plainNumber = resultNum.toByteArray();
int plainNumbersOffs = plainNumber[0] == 0 ? 1 : 0;
byte[] result = new byte[nLeadingZeros + plainNumber.length - plainNumbersOffs];
System.arraycopy(plainNumber, plainNumbersOffs, result, nLeadingZeros, plainNumber.length - plainNumbersOffs);
return result;
}
public static String encodeBase58(byte[] input) {
if (input == null) {
return null;
}
StringBuilder str = new StringBuilder((input.length * 350) / 256 + 1);
BigInteger bn = new BigInteger(1, input);
long rem;
while (true) {
BigInteger[] divideAndRemainder = bn.divideAndRemainder(BASE58_CHUNK_MOD);
bn = divideAndRemainder[0];
rem = divideAndRemainder[1].longValue();
if (bn.compareTo(BigInteger.ZERO) == 0) {
break;
}
for (int i = 0; i < BASE58_CHUNK_DIGITS; i++) {
str.append(BASE58[(int) (rem % 58)]);
rem /= 58;
}
}
while (rem != 0) {
str.append(BASE58[(int) (rem % 58)]);
rem /= 58;
}
str.reverse();
int nLeadingZeros = 0;
while (nLeadingZeros < input.length && input[nLeadingZeros] == 0) {
str.insert(0, BASE58[0]);
nLeadingZeros++;
}
return str.toString();
}
}

View file

@ -0,0 +1,78 @@
package com.tangem.domain.wallet.xrp;
import android.os.Bundle;
import android.util.Log;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import java.math.BigDecimal;
public class XrpData extends CoinData {
public XrpData() {
super();
}
private Long balanceConfirmed, balanceUnconfirmed, sequence;
@Override
public void loadFromBundle(Bundle B) {
super.loadFromBundle(B);
if (B.containsKey("BalanceConfirmed")) balanceConfirmed = B.getLong("BalanceConfirmed");
else balanceConfirmed = null;
if (B.containsKey("BalanceUnconfirmed")) balanceUnconfirmed = B.getLong("BalanceUnconfirmed");
else balanceUnconfirmed = null;
if (B.containsKey("Sequence")) sequence = B.getLong("Sequence");
}
@Override
public void saveToBundle(Bundle B) {
super.saveToBundle(B);
try {
if (balanceConfirmed != null) B.putLong("BalanceConfirmed", balanceConfirmed);
if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed);
if (sequence != null) B.putLong("Sequence", sequence);
} catch (Exception e) {
Log.e("Can't save to bundle ", e.getMessage());
}
}
@Override
public void clearInfo() {
super.clearInfo();
balanceConfirmed = null;
balanceUnconfirmed = null;
sequence = null;
}
// balanceUnconfirmed is just the latest balance, it equals balanceConfirmed if no unconfirmed transaction present
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
return new CoinEngine.InternalAmount(BigDecimal.valueOf(balanceUnconfirmed), "Drops");
}
// public Long getBalanceUnconfirmed() {
// return balanceUnconfirmed;
// }
public void setBalanceConfirmed(Long balance) {
this.balanceConfirmed = balance;
}
public void setBalanceUnconfirmed(Long balance) {
this.balanceUnconfirmed = balance;
}
public void setSequence(Long sequence) {
this.sequence = sequence;
}
public boolean hasBalanceInfo() {
return balanceConfirmed != null || balanceUnconfirmed != null;
}
public boolean hasUnconfirmed() {
return !balanceConfirmed.equals(balanceUnconfirmed);
}
}

View file

@ -0,0 +1,411 @@
package com.tangem.domain.wallet.xrp;
import android.net.Uri;
import android.text.InputFilter;
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.tangem.card_common.data.TangemCard;
import com.tangem.card_common.reader.CardProtocol;
import com.tangem.card_common.tasks.SignTask;
import com.tangem.card_common.util.Util;
import com.tangem.domain.wallet.BTCUtils;
import com.tangem.domain.wallet.BalanceValidator;
import com.tangem.domain.wallet.CoinData;
import com.tangem.domain.wallet.CoinEngine;
import com.tangem.domain.wallet.TangemContext;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.wallet.R;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
public class XrpEngine extends CoinEngine {
private static final String TAG = XrpEngine.class.getSimpleName();
public XrpData coinData = null;
public XrpEngine(TangemContext context) throws Exception {
super(context);
if (context.getCoinData() == null) {
coinData = new XrpData();
context.setCoinData(coinData);
} else if (context.getCoinData() instanceof XrpData) {
coinData = (XrpData) context.getCoinData();
} else {
throw new Exception("Invalid type of Blockchain data for XrpEngine");
}
}
public XrpEngine() {
super();
}
private static int getDecimals() {
return 6;
}
private void checkBlockchainDataExists() throws Exception {
if (coinData == null) throw new Exception("No blockchain data");
}
@Override
public boolean awaitingConfirmation() {
if (coinData == null) return false;
return coinData.hasUnconfirmed();
}
@Override
public String getBalanceHTML() {
Amount balance = getBalance();
if (balance != null) {
return balance.toDescriptionString(getDecimals());
} else {
return "";
}
}
@Override
public String getBalanceCurrency() {
return "XRP";
}
@Override
public String getOfflineBalanceHTML() {
InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
Amount offlineAmount = convertToAmount(offlineInternalAmount);
return offlineAmount.toDescriptionString(getDecimals());
}
@Override
public boolean isBalanceNotZero() {
if (coinData == null) return false;
if (coinData.getBalanceInInternalUnits() == null) return false;
return coinData.getBalanceInInternalUnits().notZero();
}
@Override
public boolean hasBalanceInfo() {
if (coinData == null) return false;
return coinData.hasBalanceInfo();
}
public boolean isExtractPossible() {
if (!hasBalanceInfo()) {
ctx.setMessage(R.string.cannot_obtain_data_from_blockchain);
} else if (!isBalanceNotZero()) {
ctx.setMessage(R.string.wallet_empty);
} else if (awaitingConfirmation()) {
ctx.setMessage(R.string.please_wait_while_previous);
} else {
return true;
}
return false;
}
@Override
public String getFeeCurrency() {
return "XRP";
}
@Override
public boolean validateAddress(String address) {
if (address == null || address.isEmpty()) {
return false;
}
if (address.length() < 25) {
return false;
}
if (address.length() > 35) {
return false;
}
if (!address.startsWith("r")) {
return false;
}
byte[] decAddress = XrpBase58.decodeBase58(address);
if (decAddress == null || decAddress.length == 0) {
return false;
}
byte[] payload = new byte[21];
System.arraycopy(decAddress, 0, payload, 0, 21);
byte[] checksum = new byte[4];
System.arraycopy(decAddress, 21, checksum, 0, 4);
byte[] calcChecksum = new byte[4];
System.arraycopy(CryptoUtil.doubleSha256(payload),0,calcChecksum,0,4);
if (!Arrays.equals(checksum, calcChecksum)) {
return false;
}
return true;
}
@Override
public boolean isNeedCheckNode() {
return true;
}
@Override
public Uri getWalletExplorerUri() {
return Uri.parse("https://xrpscan.com/account/" + ctx.getCoinData().getWallet());
}
@Override
public InputFilter[] getAmountInputFilters() {
return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())};
}
@Override
public boolean checkNewTransactionAmount(Amount amount) {
if (coinData == null) return false;
if (amount.compareTo(convertToAmount(coinData.getBalanceInInternalUnits())) > 0) {
return false;
}
return true;
}
@Override
public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) {
InternalAmount fee;
InternalAmount amount;
try {
checkBlockchainDataExists();
amount = convertToInternalAmount(amountValue);
fee = convertToInternalAmount(feeValue);
} catch (Exception e) {
e.printStackTrace();
return false;
}
if (fee == null || amount == null)
return false;
if (fee.isZero() || amount.isZero())
return false;
if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0))
return false;
if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0)
return false;
return true;
}
@Override
public boolean validateBalance(BalanceValidator balanceValidator) {
try {
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Unknown balance");
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
return false;
}
if (coinData.hasUnconfirmed()) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Transaction in progress");
balanceValidator.setSecondLine("Wait for confirmation in blockchain");
return false;
}
if (coinData.isBalanceReceived()) {// && coinData.isBalanceEqual()) { TODO:check
balanceValidator.setScore(100);
balanceValidator.setFirstLine("Verified balance");
balanceValidator.setSecondLine("Balance confirmed in blockchain");
if (coinData.getBalanceInInternalUnits().isZero()) {
balanceValidator.setFirstLine("Empty wallet");
balanceValidator.setSecondLine("");
}
}
if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) {
balanceValidator.setScore(80);
balanceValidator.setFirstLine("Verified offline balance");
balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. ");
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public Amount getBalance() {
if (!hasBalanceInfo()) return null;
return convertToAmount(coinData.getBalanceInInternalUnits());
}
@Override
public String evaluateFeeEquivalent(String fee) {
if (!coinData.getAmountEquivalentDescriptionAvailable()) return "";
try {
Amount feeAmount = new Amount(fee, getFeeCurrency());
return feeAmount.toEquivalentString(coinData.getRate());
} catch (Exception e) {
return "";
}
}
@Override
public String getBalanceEquivalent() {
if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return "";
Amount balance = getBalance();
if (balance == null) return "";
return balance.toEquivalentString(coinData.getRate());
}
public String calculateAddress(byte[] pkCompressed) throws NoSuchAlgorithmException, NoSuchProviderException, IOException {
byte[] pkForHash = canonicalPubBytes(pkCompressed);
ByteArrayOutputStream address = new ByteArrayOutputStream();
address.write((byte) 0x00);
byte[] accountId = CryptoUtil.sha256ripemd160(pkForHash);
address.write(accountId);
byte [] doubleSha256 = CryptoUtil.doubleSha256(address.toByteArray());
address.write(Arrays.copyOfRange(doubleSha256,0,4));
return BTCUtils.toHex(address.toByteArray());
}
@Override
public Amount convertToAmount(InternalAmount internalAmount) {
BigDecimal d = internalAmount.divide(new BigDecimal("1000000"));
return new Amount(d, getBalanceCurrency());
}
@Override
public Amount convertToAmount(String strAmount, String currency) {
return new Amount(strAmount, currency);
}
@Override
public InternalAmount convertToInternalAmount(Amount amount) {
BigDecimal d = amount.multiply(new BigDecimal("1000000"));
return new InternalAmount(d, "Drops");
}
@Override
public InternalAmount convertToInternalAmount(byte[] bytes) {
if (bytes == null) return null;
byte[] reversed = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
return new InternalAmount(Util.byteArrayToLong(reversed), "Drops");
}
@Override
public byte[] convertToByteArray(InternalAmount internalAmount) {
byte[] bytes = Util.longToByteArray(internalAmount.longValueExact());
// byte[] reversed = new byte[bytes.length]; TODO: check if needed
// for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1];
// return reversed;
return bytes;
}
@Override
public CoinData createCoinData() {
return new XrpData();
}
@Override
public String getUnspentInputsDescription() {
return "";
}
@Override
public void defineWallet() throws CardProtocol.TangemException {
try {
String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar());
ctx.getCoinData().setWallet(wallet);
}
catch (Exception e)
{
ctx.getCoinData().setWallet("ERROR");
throw new CardProtocol.TangemException("Can't define wallet address");
}
}
public byte[] canonicalPubBytes(byte[] pkCompressed) {
byte[] pkForHash = new byte[33];
if (pkCompressed.length == 32) {
pkForHash[0] = (byte) 0xED;
System.arraycopy(pkCompressed, 0, pkForHash, 1,32);
} else {
pkForHash = pkCompressed;
}
}
}
@Override
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
checkBlockchainDataExists();
Payment payment = new Payment();
// Put `as` AccountID field Account, `Object` o
payment.as(AccountID.Account, "rGZG674DSZJfoY8abMPSgChxZTJZEhyMRm");
payment.as(AccountID.Destination, "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK");
payment.as(com.ripple.core.coretypes.Amount.Amount, "1000000000");
payment.as(UInt32.Sequence, 10);
payment.as(com.ripple.core.coretypes.Amount.Fee, "10000");
SignedTransaction signedTx = payment.prepare(canonicalPubBytes(ctx.getCard().getWalletPublicKeyRar()));
return new SignTask.TransactionToSign() {
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod == TangemCard.SigningMethod.Sign_Hash;
}
@Override
public byte[][] getHashesToSign() {
byte[][] dataForSign = new byte[1][];
dataForSign[0] = signedTx.signingData;
return dataForSign;
}
@Override
public byte[] getRawDataToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName());
}
@Override
public String getHashAlgToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName());
}
@Override
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
throw new Exception("Transaction validation by issuer not supported in this version");
}
@Override
public byte[] onSignCompleted(byte[] signFromCard) {
signedTx.addSign(signFromCard);
return BTCUtils.fromHex(signedTx.tx_blob);
}
};
}
}

1
ripple-core/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

391
ripple-core/README.md Normal file
View file

@ -0,0 +1,391 @@
# 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)

52
ripple-core/build.gradle Normal file
View file

@ -0,0 +1,52 @@
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"
}
}

View file

@ -0,0 +1,206 @@
/**
*
* # 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}
* }
* }
* ]
* }
*/

View file

@ -0,0 +1,32 @@
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();
}
}

View file

@ -0,0 +1,126 @@
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();
}
}

View file

@ -0,0 +1,71 @@
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();
}
}
}

View file

@ -0,0 +1,110 @@
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<Hash256, CacheEntry> cache = new TreeMap<Hash256, CacheEntry>();
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;
}
}
}

View file

@ -0,0 +1,188 @@
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<AccountID> {
@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);
}

View file

@ -0,0 +1,790 @@
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<Amount>
{
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<Amount> {
@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);
}

View file

@ -0,0 +1,119 @@
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<Blob> {
@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);
}

View file

@ -0,0 +1,250 @@
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<Currency> {
@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;
}
}

View file

@ -0,0 +1,35 @@
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;
}
}

View file

@ -0,0 +1,101 @@
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<Issue> {
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()));
}
}

View file

@ -0,0 +1,36 @@
package com.ripple.core.coretypes;
import org.json.JSONObject;
import java.text.MessageFormat;
public class IssuePair implements Comparable<IssuePair> {
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;
}
}

View file

@ -0,0 +1,273 @@
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<PathSet.Path> 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<Hop> {
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<PathSet> {
@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);
}

View file

@ -0,0 +1,26 @@
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);
}
}

View file

@ -0,0 +1,82 @@
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);
}*/
}

View file

@ -0,0 +1,129 @@
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<STObject> 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<STArray> {
@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);
}

View file

@ -0,0 +1,489 @@
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<Field> {
// Internally the fields are stored in a TreeMap
public static class FieldsMap extends TreeMap<Field, SerializedType> {}
// public static class FieldsMap extends HashMap<Field, SerializedType> {}
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<Field> 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<Field, Format.Requirement> 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 <T extends HasField> 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 <T extends HasField> void putTranslated(T f, Object value) {
putTranslated(f.getField(), value);
}
public <T extends HasField> 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<Field> 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<STObject> {
@Override
public STObject fromParser(BinaryParser parser, Integer hint) {
STObject so = new STObject();
TypeTranslator<SerializedType> 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<String> 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<? extends SerializedType> 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<SerializedType> 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<SerializedType> getCastedTag(Field field) {
return (TypeTranslator<SerializedType>) field.tag;
}
}
}

View file

@ -0,0 +1,170 @@
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;
}
}

View file

@ -0,0 +1,141 @@
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<Hash256> 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<Vector256> {
@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);
}

View file

@ -0,0 +1,68 @@
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;
}
}

View file

@ -0,0 +1,155 @@
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<Subclass extends Hash> implements SerializedType, Comparable<Subclass> {
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<T extends Hash> extends TypeTranslator<T> {
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());
}
}
}

View file

@ -0,0 +1,70 @@
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<Hash128> {
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<Hash128> {
@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);
}

View file

@ -0,0 +1,81 @@
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<Hash160> {
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<Hash160> {
@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);
}

View file

@ -0,0 +1,146 @@
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<Hash256> {
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<Value> extends TreeMap<Hash256, Value> {
public Hash256Map(Hash256Map<Value> 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<Hash256> {
@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);
}

View file

@ -0,0 +1,159 @@
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<AccountID> accounts = Arrays.asList(a1, a2);
sort(accounts);
return rippleState(accounts, currency);
}
public static Hash256 rippleState(List<AccountID> 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();
}
}

View file

@ -0,0 +1,56 @@
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;
}
}

View file

@ -0,0 +1,38 @@
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();
}
}

View file

@ -0,0 +1,5 @@
package com.ripple.core.coretypes.hash.prefixes;
public interface Prefix {
byte[] bytes();
}

View file

@ -0,0 +1,209 @@
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<Subclass extends UInt> extends Number implements SerializedType, Comparable<UInt> {
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 <T extends UInt> 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<T extends UInt> extends TypeTranslator<T> {
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());
}
}
}

View file

@ -0,0 +1,106 @@
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<UInt16> {
public final static UInt16 ZERO = new UInt16(0);
public static TypeTranslator<UInt16> translate = new UINTTranslator<UInt16>() {
@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;
}
}

View file

@ -0,0 +1,137 @@
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<UInt32> {
public final static UInt32 ZERO = new UInt32(0);
public static TypeTranslator<UInt32> translate = new UINTTranslator<UInt32>() {
@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;
}
}

View file

@ -0,0 +1,112 @@
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<UInt64> {
public final static UInt64 ZERO = new UInt64(0);
public static TypeTranslator<UInt64> translate = new UINTTranslator<UInt64>() {
@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;
}
}

View file

@ -0,0 +1,105 @@
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<UInt8> {
public final static UInt8 ZERO = new UInt8(0);
public static TypeTranslator<UInt8> translate = new UINTTranslator<UInt8>() {
@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;
}
}

View file

@ -0,0 +1,29 @@
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
}

View file

@ -0,0 +1,50 @@
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);
}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class AccountIDField implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class AmountField implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class BlobField implements HasField{}

View file

@ -0,0 +1,328 @@
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<Byte> 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<Integer, Field> 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<Field> 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<Field> 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;
}
}

View file

@ -0,0 +1,5 @@
package com.ripple.core.fields;
public interface HasField {
Field getField();
}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class Hash128Field implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class Hash160Field implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class Hash256Field implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class PathSetField implements HasField{}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class STArrayField implements HasField{}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class STObjectField implements HasField{}

View file

@ -0,0 +1,35 @@
/* 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;
}
}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class UInt16Field implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class UInt32Field implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class UInt64Field implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class UInt8Field implements HasField {}

View file

@ -0,0 +1,3 @@
package com.ripple.core.fields;
public abstract class Vector256Field implements HasField{}

View file

@ -0,0 +1,66 @@
package com.ripple.core.formats;
import com.ripple.core.fields.Field;
import java.util.EnumMap;
abstract public class Format<Subclass extends Format> {
protected Format() {
}
protected void addCommonFields(){}
EnumMap<Field, Requirement> requirementEnumMap = new EnumMap<>(Field.class);
EnumMap<Field, Requirement> common = new EnumMap<>(Field.class);
public EnumMap<Field, Requirement> 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) {}
}
}

View file

@ -0,0 +1,230 @@
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<LEFormat> {
static public EnumMap<LedgerEntryType, LEFormat> formats = new EnumMap<LedgerEntryType, LEFormat>(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)
;
}

View file

@ -0,0 +1,212 @@
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<TxFormat> {
static public EnumMap<TransactionType, TxFormat> 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)
;
}

View file

@ -0,0 +1,111 @@
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;
}
}
}

View file

@ -0,0 +1,99 @@
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;
}
}

View file

@ -0,0 +1,94 @@
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);
}
}

View file

@ -0,0 +1,77 @@
package com.ripple.core.serialized;
import java.security.MessageDigest;
import java.util.ArrayList;
public class BytesList implements BytesSink {
private ArrayList<byte[]> buffer = new ArrayList<byte[]>();
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<byte[]> rawList() {
return buffer;
}
}

View file

@ -0,0 +1,8 @@
package com.ripple.core.serialized;
public interface BytesSink {
default void add(byte aByte) {
add(new byte[] {aByte});
}
void add(byte[] bytes);
}

View file

@ -0,0 +1,16 @@
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);
}
}

View file

@ -0,0 +1,11 @@
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();
}

View file

@ -0,0 +1,81 @@
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);
}
}
}

View file

@ -0,0 +1,31 @@
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);
}
}
}

View file

@ -0,0 +1,151 @@
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 <T> The SerializedType class
* TODO, this should only really have methods that each class over-rides
* it's currently pretty NASTY
*/
public abstract class TypeTranslator<T extends SerializedType> {
@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();
}
}

View file

@ -0,0 +1,258 @@
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<Integer, EngineResult> 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<EngineResult> {
@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);
}
}
}

View file

@ -0,0 +1,101 @@
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<Integer, LedgerEntryType> 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<LedgerEntryType> {
@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();
}

View file

@ -0,0 +1,103 @@
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<Integer, TransactionType> 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<TransactionType> {
@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();
}

View file

@ -0,0 +1,15 @@
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);
}
}

View file

@ -0,0 +1,59 @@
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<AccountID> owners() {
TreeSet<AccountID> 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);
}
}
}

View file

@ -0,0 +1,32 @@
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);}
}

View file

@ -0,0 +1,18 @@
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);}
}

View file

@ -0,0 +1,71 @@
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));
}
}
}

View file

@ -0,0 +1,22 @@
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);}
}

View file

@ -0,0 +1,40 @@
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);}
}

View file

@ -0,0 +1,18 @@
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);}
}

View file

@ -0,0 +1,88 @@
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());
}
}
}

View file

@ -0,0 +1,74 @@
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<Hash256> ownerDirectoryIndexes(Transaction nullableContext) {
ArrayList<Hash256> 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);}
}

View file

@ -0,0 +1,22 @@
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);}
}

View file

@ -0,0 +1,12 @@
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<Hash256> ownerDirectoryIndexes(Transaction nullableContext);
AccountID account(Transaction nullableContext);
}

View file

@ -0,0 +1,56 @@
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<Hash256> ownerDirectoryIndexes(Transaction nullableContext) {
Hash256 ownerDir = Index.ownerDirectory(account(nullableContext));
ArrayList<Hash256> 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);
}
}

View file

@ -0,0 +1,219 @@
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<Offer> qualityAscending = Comparator.comparing(Offer::directoryAskQuality);
public static Iterator<Offer> iterateCollection(Collection<STObject> offers) {
final Iterator<STObject> iterator = offers.iterator();
return new Iterator<Offer>() {
@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));
}
}

View file

@ -0,0 +1,20 @@
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());
}
}

View file

@ -0,0 +1,3 @@
package com.ripple.core.types.known.sle.entries;
public class OwnerDirectory extends DirectoryNode { }

View file

@ -0,0 +1,42 @@
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);}
}

View file

@ -0,0 +1,224 @@
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<AccountID> 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);
}
}
}

View file

@ -0,0 +1,32 @@
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);}
}

Some files were not shown because too many files have changed in this diff Show more