Update LibMobileCoin to 1.2.2.1

Fixes #12354

Co-authored-by: Bernie Dolan <bernie@mobilecoin.com>
Co-authored-by: Varsha <varsha@mobilecoin.com>
This commit is contained in:
Alex Hart
2022-07-29 15:59:52 -03:00
committed by Greyson Parrelli
parent 49cc962bde
commit f05f9287c1
8 changed files with 175 additions and 55 deletions

View File

@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mobilecoin.lib.exceptions.FogSyncException;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.PaymentDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
@@ -54,7 +56,7 @@ public final class PaymentLedgerUpdateJob extends BaseJob {
}
@Override
protected void onRun() throws IOException, RetryLaterException {
protected void onRun() throws IOException, RetryLaterException, FogSyncException {
if (!SignalStore.paymentsValues().mobileCoinPaymentsEnabled()) {
Log.w(TAG, "Payments are not enabled");
return;

View File

@@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
import com.mobilecoin.lib.ClientConfig;
import com.mobilecoin.lib.Verifier;
import com.mobilecoin.lib.exceptions.AttestationException;
import com.mobilecoin.lib.util.Hex;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.Base64;
@@ -65,31 +64,39 @@ final class MobileCoinMainNetConfig extends MobileCoinConfig {
@Override
@NonNull ClientConfig getConfig() {
try {
byte[] mrEnclaveConsensus = Hex.toByteArray("e66db38b8a43a33f6c1610d335a361963bb2b31e056af0dc0a895ac6c857cab9");
byte[] mrEnclaveConsensusNew = Hex.toByteArray("653228afd2b02a6c28f1dc3b108b1dfa457d170b32ae8ec2978f941bd1655c83");
byte[] mrEnclaveReport = Hex.toByteArray("709ab90621e3a8d9eb26ed9e2830e091beceebd55fb01c5d7c31d27e83b9b0d1");
byte[] mrEnclaveReportNew = Hex.toByteArray("f3f7e9a674c55fb2af543513527b6a7872de305bac171783f6716a0bf6919499");
byte[] mrEnclaveLedger = Hex.toByteArray("511eab36de691ded50eb08b173304194da8b9d86bfdd7102001fe6bb279c3666");
byte[] mrEnclaveLedgerNew = Hex.toByteArray("89db0d1684fcc98258295c39f4ab68f7de5917ef30f0004d9a86f29930cebbbd");
byte[] mrEnclaveView = Hex.toByteArray("ddd59da874fdf3239d5edb1ef251df07a8728c9ef63057dd0b50ade5a9ddb041");
byte[] mrEnclaveViewNew = Hex.toByteArray("dd84abda7f05116e21fcd1ee6361b0ec29445fff0472131eaf37bf06255b567a");
Set<X509Certificate> trustRoots = getTrustRoots(R.raw.signal_mobilecoin_authority);
ClientConfig config = new ClientConfig();
String[] hardeningAdvisories = { "INTEL-SA-00334" };
VerifierFactory verifierFactory = new VerifierFactory(hardeningAdvisories,
new ServiceConfig(
"e66db38b8a43a33f6c1610d335a361963bb2b31e056af0dc0a895ac6c857cab9",
"709ab90621e3a8d9eb26ed9e2830e091beceebd55fb01c5d7c31d27e83b9b0d1",
"511eab36de691ded50eb08b173304194da8b9d86bfdd7102001fe6bb279c3666",
"ddd59da874fdf3239d5edb1ef251df07a8728c9ef63057dd0b50ade5a9ddb041"
),
new ServiceConfig(
"653228afd2b02a6c28f1dc3b108b1dfa457d170b32ae8ec2978f941bd1655c83",
"f3f7e9a674c55fb2af543513527b6a7872de305bac171783f6716a0bf6919499",
"89db0d1684fcc98258295c39f4ab68f7de5917ef30f0004d9a86f29930cebbbd",
"dd84abda7f05116e21fcd1ee6361b0ec29445fff0472131eaf37bf06255b567a"
),
new ServiceConfig(
"733080d6ece4504f66ba606fa8163dae0a5220f3dbf6ca55fbafbac12c6f1897",
"660103d766cde0fd1e1cfb443b99e52da2ce0617d0dee42f8b875f7104942c6b",
"ed8ed6e1b4b6827e5543b25c1c13b9c06b478d819f8df912eb11fa140780fc51",
"c64a3b04348b10596442868758875f312dc3a755b450805149774a091d2822d3"
));
config.logAdapter = new MobileCoinLogAdapter();
config.fogView = new ClientConfig.Service().withTrustRoots(trustRoots)
.withVerifier(new Verifier().withMrEnclave(mrEnclaveView, null, hardeningAdvisories)
.withMrEnclave(mrEnclaveViewNew, null, hardeningAdvisories));
.withVerifier(verifierFactory.createViewVerifier());
config.fogLedger = new ClientConfig.Service().withTrustRoots(trustRoots)
.withVerifier(new Verifier().withMrEnclave(mrEnclaveLedger, null, hardeningAdvisories)
.withMrEnclave(mrEnclaveLedgerNew, null, hardeningAdvisories));
.withVerifier(verifierFactory.createLedgerVerifier());
config.consensus = new ClientConfig.Service().withTrustRoots(trustRoots)
.withVerifier(new Verifier().withMrEnclave(mrEnclaveConsensus, null, hardeningAdvisories)
.withMrEnclave(mrEnclaveConsensusNew, null, hardeningAdvisories));
config.report = new ClientConfig.Service().withVerifier(new Verifier().withMrEnclave(mrEnclaveReport, null, hardeningAdvisories)
.withMrEnclave(mrEnclaveReportNew, null, hardeningAdvisories));
.withVerifier(verifierFactory.createConsensusVerifier());
config.report = new ClientConfig.Service().withVerifier(verifierFactory.createReportVerifier());
return config;
} catch (AttestationException ex) {
throw new IllegalStateException();

View File

@@ -49,18 +49,7 @@ final class MobileCoinTestNetConfig extends MobileCoinConfig {
@Override
@NonNull byte[] getFogAuthoritySpki() {
return Base64.decodeOrThrow("MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoCMq8nnjTq5EEQ4EI7yr\n"
+ "ABL9P4y4h1P/h0DepWgXx+w/fywcfRSZINxbaMpvcV3uSJayExrpV1KmaS2wfASe\n"
+ "YhSj+rEzAm0XUOw3Q94NOx5A/dOQag/d1SS6/QpF3PQYZTULnRFetmM4yzEnXsXc\n"
+ "WtzEu0hh02wYJbLeAq4CCcPTPe2qckrbUP9sD18/KOzzNeypF4p5dQ2m/ezfxtga\n"
+ "LvdUMVDVIAs2v9a5iu6ce4bIcwTIUXgX0w3+UKRx8zqowc3HIqo9yeaGn4ZOwQHv\n"
+ "AJZecPmb2pH1nK+BtDUvHpvf+Y3/NJxwh+IPp6Ef8aoUxs2g5oIBZ3Q31fjS2Bh2\n"
+ "gmwoVooyytEysPAHvRPVBxXxLi36WpKfk1Vq8K7cgYh3IraOkH2/l2Pyi8EYYFkW\n"
+ "sLYofYogaiPzVoq2ZdcizfoJWIYei5mgq+8m0ZKZYLebK1i2GdseBJNIbSt3wCNX\n"
+ "ZxyN6uqFHOCB29gmA5cbKvs/j9mDz64PJe9LCanqcDQV1U5l9dt9UdmUt7Ab1PjB\n"
+ "toIFaP+u473Z0hmZdCgAivuiBMMYMqt2V2EIw4IXLASE3roLOYp0p7h0IQHb+lVI\n"
+ "uEl0ZmwAI30ZmzgcWc7RBeWD1/zNt55zzhfPRLx/DfDY5Kdp6oFHWMvI2r1/oZkd\n"
+ "hjFp7pV6qrl7vOyR5QqmuRkCAwEAAQ==");
return Base64.decodeOrThrow("MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoCMq8nnjTq5EEQ4EI7yrABL9P4y4h1P/h0DepWgXx+w/fywcfRSZINxbaMpvcV3uSJayExrpV1KmaS2wfASeYhSj+rEzAm0XUOw3Q94NOx5A/dOQag/d1SS6/QpF3PQYZTULnRFetmM4yzEnXsXcWtzEu0hh02wYJbLeAq4CCcPTPe2qckrbUP9sD18/KOzzNeypF4p5dQ2m/ezfxtgaLvdUMVDVIAs2v9a5iu6ce4bIcwTIUXgX0w3+UKRx8zqowc3HIqo9yeaGn4ZOwQHvAJZecPmb2pH1nK+BtDUvHpvf+Y3/NJxwh+IPp6Ef8aoUxs2g5oIBZ3Q31fjS2Bh2gmwoVooyytEysPAHvRPVBxXxLi36WpKfk1Vq8K7cgYh3IraOkH2/l2Pyi8EYYFkWsLYofYogaiPzVoq2ZdcizfoJWIYei5mgq+8m0ZKZYLebK1i2GdseBJNIbSt3wCNXZxyN6uqFHOCB29gmA5cbKvs/j9mDz64PJe9LCanqcDQV1U5l9dt9UdmUt7Ab1PjBtoIFaP+u473Z0hmZdCgAivuiBMMYMqt2V2EIw4IXLASE3roLOYp0p7h0IQHb+lVIuEl0ZmwAI30ZmzgcWc7RBeWD1/zNt55zzhfPRLx/DfDY5Kdp6oFHWMvI2r1/oZkdhjFp7pV6qrl7vOyR5QqmuRkCAwEAAQ==");
}
@Override
@@ -71,10 +60,10 @@ final class MobileCoinTestNetConfig extends MobileCoinConfig {
@Override
@NonNull ClientConfig getConfig() {
try {
byte[] mrEnclaveConsensus = Hex.toByteArray("9659ea738275b3999bf1700398b60281be03af5cb399738a89b49ea2496595af");
byte[] mrEnclaveReport = Hex.toByteArray("a4764346f91979b4906d4ce26102228efe3aba39216dec1e7d22e6b06f919f11");
byte[] mrEnclaveLedger = Hex.toByteArray("768f7bea6171fb83d775ee8485e4b5fcebf5f664ca7e8b9ceef9c7c21e9d9bf3");
byte[] mrEnclaveView = Hex.toByteArray("e154f108c7758b5aa7161c3824c176f0c20f63012463bf3cc5651e678f02fb9e");
byte[] mrEnclaveConsensus = Hex.toByteArray("4f134dcfd9c0885956f2f9af0f05c2050d8bdee2dc63b468a640670d7adeb7f8");
byte[] mrEnclaveReport = Hex.toByteArray("8f2f3bf81f24bf493fa6d76e29e0f081815022592b1e854f95bda750aece7452");
byte[] mrEnclaveLedger = Hex.toByteArray("685481b33f2846585f33506ab65649c98a4a6d1244989651fd0fcde904ebd82f");
byte[] mrEnclaveView = Hex.toByteArray("719ca43abbe02f507bb91ea11ff8bc900aa86363a7d7e77b8130426fc53d8684");
byte[] mrSigner = Hex.toByteArray("bf7fa957a6a94acb588851bc8767e0ca57706c79f4fc2aa6bcb993012c3c386c");
Set<X509Certificate> trustRoots = getTrustRoots(R.raw.signal_mobilecoin_authority);
ClientConfig config = new ClientConfig();

View File

@@ -0,0 +1,19 @@
package org.thoughtcrime.securesms.payments
import com.mobilecoin.lib.util.Hex
/**
* Represents the service configuration values for a given MobileCoin config, used to build
* Verifiers.
*/
class ServiceConfig(
consensus: String,
report: String,
ledger: String,
view: String
) {
val consensus: ByteArray = Hex.toByteArray(consensus)
val report: ByteArray = Hex.toByteArray(report)
val ledger: ByteArray = Hex.toByteArray(ledger)
val view: ByteArray = Hex.toByteArray(view)
}

View File

@@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.payments
import com.mobilecoin.lib.Verifier
import com.mobilecoin.lib.exceptions.AttestationException
/**
* Wraps the given service configurations and provides methods to grab a fully constructed verifier instance.
* This is to ease the addition of new service configurations moving forward, which simply need a new ServiceConfig object
* to be added to the given list.
*/
class VerifierFactory(private val hardeningAdvisories: Array<String>, private vararg val serviceConfigs: ServiceConfig) {
@Throws(AttestationException::class)
fun createConsensusVerifier(): Verifier {
return createVerifier(ServiceConfig::consensus)
}
@Throws(AttestationException::class)
fun createLedgerVerifier(): Verifier {
return createVerifier(ServiceConfig::ledger)
}
@Throws(AttestationException::class)
fun createViewVerifier(): Verifier {
return createVerifier(ServiceConfig::view)
}
@Throws(AttestationException::class)
fun createReportVerifier(): Verifier {
return createVerifier(ServiceConfig::report)
}
@Throws(AttestationException::class)
private fun createVerifier(getConfigValue: (ServiceConfig) -> ByteArray): Verifier {
return serviceConfigs.fold(Verifier()) { verifier, config ->
verifier.withMrEnclave(getConfigValue(config), null, hardeningAdvisories)
}
}
}

View File

@@ -8,18 +8,22 @@ import androidx.annotation.WorkerThread;
import com.google.protobuf.ByteString;
import com.mobilecoin.lib.AccountKey;
import com.mobilecoin.lib.AccountSnapshot;
import com.mobilecoin.lib.Amount;
import com.mobilecoin.lib.DefragmentationDelegate;
import com.mobilecoin.lib.MobileCoinClient;
import com.mobilecoin.lib.OwnedTxOut;
import com.mobilecoin.lib.PendingTransaction;
import com.mobilecoin.lib.Receipt;
import com.mobilecoin.lib.TokenId;
import com.mobilecoin.lib.Transaction;
import com.mobilecoin.lib.TxOutMemoBuilder;
import com.mobilecoin.lib.UnsignedLong;
import com.mobilecoin.lib.exceptions.AmountDecoderException;
import com.mobilecoin.lib.exceptions.AttestationException;
import com.mobilecoin.lib.exceptions.BadEntropyException;
import com.mobilecoin.lib.exceptions.FeeRejectedException;
import com.mobilecoin.lib.exceptions.FogReportException;
import com.mobilecoin.lib.exceptions.FogSyncException;
import com.mobilecoin.lib.exceptions.FragmentedAccountException;
import com.mobilecoin.lib.exceptions.InsufficientFundsException;
import com.mobilecoin.lib.exceptions.InvalidFogResponse;
@@ -29,6 +33,7 @@ import com.mobilecoin.lib.exceptions.InvalidUriException;
import com.mobilecoin.lib.exceptions.NetworkException;
import com.mobilecoin.lib.exceptions.SerializationException;
import com.mobilecoin.lib.exceptions.TransactionBuilderException;
import com.mobilecoin.lib.network.TransportProtocol;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.keyvalue.PaymentsValues;
@@ -65,7 +70,8 @@ public final class Wallet {
this.mobileCoinClient = new MobileCoinClient(account,
mobileCoinConfig.getFogUri(),
mobileCoinConfig.getConsensusUri(),
mobileCoinConfig.getConfig());
mobileCoinConfig.getConfig(),
TransportProtocol.forGRPC());
} catch (InvalidUriException | BadEntropyException e) {
throw new AssertionError(e);
}
@@ -102,7 +108,7 @@ public final class Wallet {
MobileCoinLedgerWrapper ledger = tryGetFullLedger(null);
paymentsValues.setMobileCoinFullLedger(Objects.requireNonNull(ledger));
} catch (IOException e) {
} catch (IOException | FogSyncException e) {
if ((retryOnAuthFailure && e.getCause() instanceof NetworkException) &&
(((NetworkException) e.getCause()).statusCode == 401))
{
@@ -117,7 +123,7 @@ public final class Wallet {
}
@WorkerThread
public @Nullable MobileCoinLedgerWrapper tryGetFullLedger(@Nullable Long minimumBlockIndex) throws IOException {
public @Nullable MobileCoinLedgerWrapper tryGetFullLedger(@Nullable Long minimumBlockIndex) throws IOException, FogSyncException {
try {
MobileCoinLedger.Builder builder = MobileCoinLedger.newBuilder();
BigInteger totalUnspent = BigInteger.ZERO;
@@ -125,7 +131,7 @@ public final class Wallet {
UnsignedLong highestBlockIndex = UnsignedLong.ZERO;
final long asOfTimestamp = System.currentTimeMillis();
AccountSnapshot accountSnapshot = mobileCoinClient.getAccountSnapshot();
final BigInteger minimumTxFee = mobileCoinClient.getOrFetchMinimumTxFee();
final Amount minimumTxFee = mobileCoinClient.getOrFetchMinimumTxFee(TokenId.MOB);
if (minimumBlockIndex != null) {
long snapshotBlockIndex = accountSnapshot.getBlockIndex().longValue();
@@ -135,9 +141,10 @@ public final class Wallet {
}
}
for (OwnedTxOut txOut : accountSnapshot.getAccountActivity().getAllTxOuts()) {
for (OwnedTxOut txOut : accountSnapshot.getAccountActivity().getAllTokenTxOuts(TokenId.MOB)) {
final Amount txOutAmount = txOut.getAmount();
MobileCoinLedger.OwnedTXO.Builder txoBuilder = MobileCoinLedger.OwnedTXO.newBuilder()
.setAmount(Uint64Util.bigIntegerToUInt64(txOut.getValue()))
.setAmount(Uint64Util.bigIntegerToUInt64(txOutAmount.getValue()))
.setReceivedInBlock(getBlock(txOut.getReceivedBlockIndex(), txOut.getReceivedBlockTimestamp()))
.setKeyImage(ByteString.copyFrom(txOut.getKeyImage().getData()))
.setPublicKey(ByteString.copyFrom(txOut.getPublicKey().getKeyBytes()));
@@ -147,7 +154,7 @@ public final class Wallet {
txoBuilder.setSpentInBlock(getBlock(txOut.getSpentBlockIndex(), txOut.getSpentBlockTimestamp()));
builder.addSpentTxos(txoBuilder);
} else {
totalUnspent = totalUnspent.add(txOut.getValue());
totalUnspent = totalUnspent.add(txOutAmount.getValue());
builder.addUnspentTxos(txoBuilder);
}
@@ -168,7 +175,7 @@ public final class Wallet {
}
}
builder.setBalance(Uint64Util.bigIntegerToUInt64(totalUnspent))
.setTransferableBalance(Uint64Util.bigIntegerToUInt64(accountSnapshot.getTransferableAmount(minimumTxFee)))
.setTransferableBalance(Uint64Util.bigIntegerToUInt64(accountSnapshot.getTransferableAmount(minimumTxFee).getValue()))
.setAsOfTimeStamp(asOfTimestamp)
.setHighestBlock(MobileCoinLedger.Block.newBuilder()
.setBlockNumber(highestBlockIndex.longValue())
@@ -206,11 +213,11 @@ public final class Wallet {
public @NonNull Money.MobileCoin getFee(@NonNull Money.MobileCoin amount) throws IOException {
try {
BigInteger picoMob = amount.requireMobileCoin().toPicoMobBigInteger();
return Money.picoMobileCoin(mobileCoinClient.estimateTotalFee(picoMob));
return Money.picoMobileCoin(mobileCoinClient.estimateTotalFee(Amount.ofMOB(picoMob)).getValue());
} catch (InvalidFogResponse | AttestationException | InsufficientFundsException e) {
Log.w(TAG, "Failed to get fee", e);
return Money.MobileCoin.ZERO;
} catch (NetworkException e) {
} catch (NetworkException | FogSyncException e) {
Log.w(TAG, "Failed to get fee", e);
throw new IOException(e);
}
@@ -227,7 +234,7 @@ public final class Wallet {
}
@WorkerThread
public @NonNull TransactionStatusResult getSentTransactionStatus(@NonNull PaymentTransactionId transactionId) throws IOException {
public @NonNull TransactionStatusResult getSentTransactionStatus(@NonNull PaymentTransactionId transactionId) throws IOException, FogSyncException {
try {
PaymentTransactionId.MobileCoin mobcoinTransaction = (PaymentTransactionId.MobileCoin) transactionId;
Transaction transaction = Transaction.fromBytes(mobcoinTransaction.getTransaction());
@@ -255,7 +262,7 @@ public final class Wallet {
}
@WorkerThread
public @NonNull ReceivedTransactionStatus getReceivedTransactionStatus(@NonNull byte[] receiptBytes) throws IOException {
public @NonNull ReceivedTransactionStatus getReceivedTransactionStatus(@NonNull byte[] receiptBytes) throws IOException, FogSyncException {
try {
Receipt receipt = Receipt.fromBytes(receiptBytes);
Receipt.Status status = mobileCoinClient.getReceiptStatus(receipt);
@@ -266,8 +273,8 @@ public final class Wallet {
case FAILED:
return ReceivedTransactionStatus.failed();
case RECEIVED:
BigInteger amount = receipt.getAmount(account);
return ReceivedTransactionStatus.complete(Money.picoMobileCoin(amount), status.getBlockIndex().longValue());
final Amount amount = receipt.getAmountData(account);
return ReceivedTransactionStatus.complete(Money.picoMobileCoin(amount.getValue()), status.getBlockIndex().longValue());
default:
throw new IllegalStateException("Unknown Transaction Status: " + status);
}
@@ -297,7 +304,7 @@ public final class Wallet {
Log.w(TAG, "Insufficient funds", e);
results.add(TransactionSubmissionResult.failure(TransactionSubmissionResult.ErrorCode.INSUFFICIENT_FUNDS, true));
return;
} catch (TimeoutException | InvalidTransactionException | InvalidFogResponse | AttestationException | TransactionBuilderException | NetworkException | FogReportException e) {
} catch (TimeoutException | InvalidTransactionException | InvalidFogResponse | AttestationException | TransactionBuilderException | NetworkException | FogReportException | FogSyncException e) {
Log.w(TAG, "Defragment failed", e);
results.add(TransactionSubmissionResult.failure(TransactionSubmissionResult.ErrorCode.GENERIC_FAILURE, true));
return;
@@ -318,8 +325,9 @@ public final class Wallet {
try {
pendingTransaction = mobileCoinClient.prepareTransaction(to.getAddress(),
picoMob,
feeMobileCoin.toPicoMobBigInteger());
Amount.ofMOB(picoMob),
Amount.ofMOB(feeMobileCoin.toPicoMobBigInteger()),
TxOutMemoBuilder.createSenderAndDestinationRTHMemoBuilder(account));
} catch (InsufficientFundsException e) {
Log.w(TAG, "Insufficient funds", e);
results.add(TransactionSubmissionResult.failure(TransactionSubmissionResult.ErrorCode.INSUFFICIENT_FUNDS, false));
@@ -346,6 +354,9 @@ public final class Wallet {
} catch (TransactionBuilderException e) {
Log.w(TAG, "Builder problem", e);
results.add(TransactionSubmissionResult.failure(TransactionSubmissionResult.ErrorCode.GENERIC_FAILURE, false));
} catch(FogSyncException e) {
Log.w(TAG, "Fog currently out of sync", e);
results.add(TransactionSubmissionResult.failure(TransactionSubmissionResult.ErrorCode.NETWORK_FAILURE, false));
}
if (pendingTransaction == null) {
@@ -379,11 +390,11 @@ public final class Wallet {
*/
@WorkerThread
private @NonNull Money.MobileCoin defragment(@NonNull Money.MobileCoin amount, @NonNull List<TransactionSubmissionResult> results)
throws TransactionBuilderException, NetworkException, InvalidTransactionException, AttestationException, FogReportException, InvalidFogResponse, TimeoutException, InsufficientFundsException
throws TransactionBuilderException, NetworkException, InvalidTransactionException, AttestationException, FogReportException, InvalidFogResponse, TimeoutException, InsufficientFundsException, FogSyncException
{
Log.i(TAG, "Defragmenting account");
DefragDelegate defragDelegate = new DefragDelegate(mobileCoinClient, results);
mobileCoinClient.defragmentAccount(amount.toPicoMobBigInteger(), defragDelegate);
mobileCoinClient.defragmentAccount(Amount.ofMOB(amount.toPicoMobBigInteger()), defragDelegate, true);
Log.i(TAG, "Account defragmented at a cost of " + defragDelegate.totalFeesSpent);
return defragDelegate.totalFeesSpent;
}