Updated on 2026-08-14
This commit is contained in:
parent
ecc6243780
commit
241b29c5df
170 changed files with 13844 additions and 6 deletions
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':ripple-core')
|
||||
implementation project(':card-common')
|
||||
implementation project(':card-android')
|
||||
implementation project(':server-android')
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
15
app/src/main/java/com/tangem/data/network/RippleApi.java
Normal file
15
app/src/main/java/com/tangem/data/network/RippleApi.java
Normal 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);
|
||||
}
|
||||
114
app/src/main/java/com/tangem/data/network/ServerApiRipple.java
Normal file
114
app/src/main/java/com/tangem/data/network/ServerApiRipple.java
Normal 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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
115
app/src/main/java/com/tangem/domain/wallet/xrp/XrpBase58.java
Normal file
115
app/src/main/java/com/tangem/domain/wallet/xrp/XrpBase58.java
Normal 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();
|
||||
}
|
||||
}
|
||||
78
app/src/main/java/com/tangem/domain/wallet/xrp/XrpData.java
Normal file
78
app/src/main/java/com/tangem/domain/wallet/xrp/XrpData.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
411
app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java
Normal file
411
app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java
Normal 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
1
ripple-core/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
391
ripple-core/README.md
Normal file
391
ripple-core/README.md
Normal 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
52
ripple-core/build.gradle
Normal 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"
|
||||
}
|
||||
}
|
||||
206
ripple-core/src/main/java/README.java
Normal file
206
ripple-core/src/main/java/README.java
Normal 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}
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
126
ripple-core/src/main/java/com/ripple/core/binary/STReader.java
Normal file
126
ripple-core/src/main/java/com/ripple/core/binary/STReader.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
110
ripple-core/src/main/java/com/ripple/core/cache/SLECache.java
vendored
Normal file
110
ripple-core/src/main/java/com/ripple/core/cache/SLECache.java
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
790
ripple-core/src/main/java/com/ripple/core/coretypes/Amount.java
Normal file
790
ripple-core/src/main/java/com/ripple/core/coretypes/Amount.java
Normal 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);
|
||||
|
||||
}
|
||||
119
ripple-core/src/main/java/com/ripple/core/coretypes/Blob.java
Normal file
119
ripple-core/src/main/java/com/ripple/core/coretypes/Blob.java
Normal 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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
101
ripple-core/src/main/java/com/ripple/core/coretypes/Issue.java
Normal file
101
ripple-core/src/main/java/com/ripple/core/coretypes/Issue.java
Normal 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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
273
ripple-core/src/main/java/com/ripple/core/coretypes/PathSet.java
Normal file
273
ripple-core/src/main/java/com/ripple/core/coretypes/PathSet.java
Normal 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}*/
|
||||
}
|
||||
129
ripple-core/src/main/java/com/ripple/core/coretypes/STArray.java
Normal file
129
ripple-core/src/main/java/com/ripple/core/coretypes/STArray.java
Normal 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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.ripple.core.coretypes.hash.prefixes;
|
||||
|
||||
public interface Prefix {
|
||||
byte[] bytes();
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class AccountIDField implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class AmountField implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class BlobField implements HasField{}
|
||||
328
ripple-core/src/main/java/com/ripple/core/fields/Field.java
Normal file
328
ripple-core/src/main/java/com/ripple/core/fields/Field.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public interface HasField {
|
||||
Field getField();
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class Hash128Field implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class Hash160Field implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class Hash256Field implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class PathSetField implements HasField{}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class STArrayField implements HasField{}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class STObjectField implements HasField{}
|
||||
35
ripple-core/src/main/java/com/ripple/core/fields/Type.java
Normal file
35
ripple-core/src/main/java/com/ripple/core/fields/Type.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class UInt16Field implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class UInt32Field implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class UInt64Field implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class UInt8Field implements HasField {}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.fields;
|
||||
|
||||
public abstract class Vector256Field implements HasField{}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
230
ripple-core/src/main/java/com/ripple/core/formats/LEFormat.java
Normal file
230
ripple-core/src/main/java/com/ripple/core/formats/LEFormat.java
Normal 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)
|
||||
;
|
||||
|
||||
}
|
||||
212
ripple-core/src/main/java/com/ripple/core/formats/TxFormat.java
Normal file
212
ripple-core/src/main/java/com/ripple/core/formats/TxFormat.java
Normal 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)
|
||||
;
|
||||
}
|
||||
111
ripple-core/src/main/java/com/ripple/core/runtime/Value.java
Normal file
111
ripple-core/src/main/java/com/ripple/core/runtime/Value.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);}
|
||||
}
|
||||
|
|
@ -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);}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);}
|
||||
|
||||
}
|
||||
|
|
@ -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);}
|
||||
}
|
||||
|
|
@ -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);}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);}
|
||||
|
||||
}
|
||||
|
|
@ -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);}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.ripple.core.types.known.sle.entries;
|
||||
|
||||
public class OwnerDirectory extends DirectoryNode { }
|
||||
|
|
@ -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);}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue