Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SorobanDataBuilder to prepare sorobanData easily. #509

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions src/main/java/org/stellar/sdk/SorobanDataBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package org.stellar.sdk;

import java.io.IOException;
import java.util.Collection;
import javax.annotation.Nullable;
import org.stellar.sdk.xdr.ExtensionPoint;
import org.stellar.sdk.xdr.Int64;
import org.stellar.sdk.xdr.LedgerFootprint;
import org.stellar.sdk.xdr.LedgerKey;
import org.stellar.sdk.xdr.SorobanResources;
import org.stellar.sdk.xdr.SorobanTransactionData;
import org.stellar.sdk.xdr.Uint32;
import org.stellar.sdk.xdr.XdrUnsignedInteger;

/**
* Supports building {@link SorobanTransactionData} structures with various items set to specific
* values.
*
* <p>This is recommended for when you are building {@link BumpFootprintExpirationOperation} and
* {@link RestoreFootprintOperation} operations to avoid (re)building the entire data structure from
* scratch.
*/
public class SorobanDataBuilder {
private final SorobanTransactionData data;

/** Creates a new builder with an empty {@link SorobanTransactionData}. */
public SorobanDataBuilder() {
data =
new SorobanTransactionData.Builder()
.resources(
new SorobanResources.Builder()
.footprint(
new LedgerFootprint.Builder()
.readOnly(new LedgerKey[] {})
.readWrite(new LedgerKey[] {})
.build())
.instructions(new Uint32(new XdrUnsignedInteger(0)))
.readBytes(new Uint32(new XdrUnsignedInteger(0)))
.writeBytes(new Uint32(new XdrUnsignedInteger(0)))
.extendedMetaDataSizeBytes(new Uint32(new XdrUnsignedInteger(0)))
.build())
.refundableFee(new Int64(0L))
.ext(new ExtensionPoint.Builder().discriminant(0).build())
.build();
}

/**
* Creates a new builder from a base64 representation of {@link SorobanTransactionData}.
*
* @param sorobanData base64 representation of {@link SorobanTransactionData}
*/
public SorobanDataBuilder(String sorobanData) {
try {
data = SorobanTransactionData.fromXdrBase64(sorobanData);
} catch (IOException e) {
throw new IllegalArgumentException("Invalid SorobanData: " + sorobanData, e);
}
}

/**
* Creates a new builder from a {@link SorobanTransactionData}.
*
* @param sorobanData {@link SorobanTransactionData}.
*/
public SorobanDataBuilder(SorobanTransactionData sorobanData) {
try {
data = SorobanTransactionData.fromXdrByteArray(sorobanData.toXdrByteArray());
} catch (IOException e) {
throw new IllegalArgumentException("Invalid SorobanData: " + sorobanData, e);
}
}

/**
* Sets the "refundable" fee portion of the Soroban data.
*
* @param fee the refundable fee to set (int64)
* @return this builder instance
*/
public SorobanDataBuilder setRefundableFee(long fee) {
data.setRefundableFee(new Int64(fee));
return this;
}

/**
* Sets up the resource metrics.
*
* <p>You should almost NEVER need this, as its often generated/provided to you by transaction
* simulation/preflight from a Soroban RPC server.
*
* @param cpuInstructions number of CPU instructions (uint32)
* @param readBytes number of bytes being read (uint32)
* @param writeBytes number of bytes being written (uint32)
* @param metadataBytes number of extended metadata bytes (uint32)
* @return this builder instance
*/
public SorobanDataBuilder setResources(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because all the parameters have the same type I think it could lead to mistakes where you pass in the parameters in the wrong order. Is it possible to have a Resources builder object that could be passed into setResources()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, fixed in 9f5f231

long cpuInstructions, long readBytes, long writeBytes, long metadataBytes) {
data.getResources().setInstructions(new Uint32(new XdrUnsignedInteger(cpuInstructions)));
data.getResources().setReadBytes(new Uint32(new XdrUnsignedInteger(readBytes)));
data.getResources().setWriteBytes(new Uint32(new XdrUnsignedInteger(writeBytes)));
data.getResources()
.setExtendedMetaDataSizeBytes(new Uint32(new XdrUnsignedInteger(metadataBytes)));
return this;
}

/**
* Sets the storage access footprint to be a certain set of ledger keys.
*
* <p>You can also set each field explicitly via {@link
* SorobanDataBuilder#setReadOnly(Collection)} and {@link
* SorobanDataBuilder#setReadWrite(Collection)}.
*
* <p>Passing {@code null} to either parameter will leave that portion of the footprint untouched.
* If you want to clear a portion of the footprint, pass an empty collection.
*
* @param readOnly the set of ledger keys to set in the read-only portion of the transaction's
* sorobanData
* @param readWrite the set of ledger keys to set in the read-write portion of the transaction's
* sorobanData
* @return this builder instance
*/
public SorobanDataBuilder setFootprint(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because all the parameters have the same type I think it could lead to mistakes where you pass in the parameters in the wrong order. Is it possible to have a footprint builder object that could be passed into setFootprint()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If necessary, we can remove this method and only keep setReadOnly and setReadOnly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's an even better idea.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4da8f81

@Nullable Collection<LedgerKey> readOnly, @Nullable Collection<LedgerKey> readWrite) {
if (readOnly != null) {
data.getResources().getFootprint().setReadOnly(readOnly.toArray(new LedgerKey[0]));
}
if (readWrite != null) {
data.getResources().getFootprint().setReadWrite(readWrite.toArray(new LedgerKey[0]));
}
return this;
}

/**
* Sets the read-only portion of the storage access footprint to be a certain set of ledger keys.
*
* <p>Passing {@code null} will leave that portion of the footprint untouched. If you want to
* clear a portion of the footprint, pass an empty collection.
*
* @param readOnly the set of ledger keys to set in the read-only portion of the transaction's
* sorobanData
* @return this builder instance
*/
public SorobanDataBuilder setReadOnly(@Nullable Collection<LedgerKey> readOnly) {
if (readOnly != null) {
data.getResources().getFootprint().setReadOnly(readOnly.toArray(new LedgerKey[0]));
}
return this;
}

/**
* Sets the read-write portion of the storage access footprint to be a certain set of ledger keys.
*
* <p>Passing {@code null} will leave that portion of the footprint untouched. If you want to
* clear a portion of the footprint, pass an empty collection.
*
* @param readWrite the set of ledger keys to set in the read-write portion of the transaction's
* sorobanData
* @return this builder instance
*/
public SorobanDataBuilder setReadWrite(@Nullable Collection<LedgerKey> readWrite) {
if (readWrite != null) {
data.getResources().getFootprint().setReadWrite(readWrite.toArray(new LedgerKey[0]));
}
return this;
}

/**
* @return the copy of the final {@link SorobanTransactionData}.
*/
public SorobanTransactionData build() {
try {
return SorobanTransactionData.fromXdrByteArray(data.toXdrByteArray());
} catch (IOException e) {
throw new IllegalArgumentException("Copy SorobanData failed, please report this bug.", e);
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/stellar/sdk/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class Transaction extends AbstractTransaction {
this.mPreconditions = preconditions;
this.mFee = fee;
this.mMemo = memo != null ? memo : Memo.none();
this.mSorobanData = sorobanData;
this.mSorobanData = sorobanData != null ? new SorobanDataBuilder(sorobanData).build() : null;
}

// setEnvelopeType is only used in tests which is why this method is package protected
Expand Down
28 changes: 17 additions & 11 deletions src/main/java/org/stellar/sdk/TransactionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static org.stellar.sdk.TransactionPreconditions.TIMEOUT_INFINITE;

import com.google.common.base.Function;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -264,29 +263,36 @@ public static org.stellar.sdk.xdr.TimeBounds buildTimeBounds(long minTime, long
}

/**
* Sets Soroban data to the transaction. TODO: After adding SorobanServer, add more descriptions.
* Sets the transaction's internal Soroban transaction data (resources, footprint, etc.).
*
* <p>For non-contract(non-Soroban) transactions, this setting has no effect. In the case of
* Soroban transactions, this is either an instance of {@link SorobanTransactionData} or a
* base64-encoded string of said structure. This is usually obtained from the simulation response
* based on a transaction with a Soroban operation (e.g. {@link InvokeHostFunctionOperation},
* providing necessary resource and storage footprint estimations for contract invocation.
*
* @param sorobanData Soroban data to set
* @return Builder object so you can chain methods.
*/
public TransactionBuilder setSorobanData(SorobanTransactionData sorobanData) {
this.mSorobanData = sorobanData;
this.mSorobanData = new SorobanDataBuilder(sorobanData).build();
return this;
}

/**
* Sets Soroban data to the transaction. TODO: After adding SorobanServer, add more descriptions.
* Sets the transaction's internal Soroban transaction data (resources, footprint, etc.).
*
* <p>For non-contract(non-Soroban) transactions, this setting has no effect. In the case of
* Soroban transactions, this is either an instance of {@link SorobanTransactionData} or a
* base64-encoded string of said structure. This is usually obtained from the simulation response
* based on a transaction with a Soroban operation (e.g. {@link InvokeHostFunctionOperation},
* providing necessary resource and storage footprint estimations for contract invocation.
*
* @param sorobanData Soroban data to set
* @return Builder object so you can chain methods.
*/
public TransactionBuilder setSorobanData(String sorobanData) {
SorobanTransactionData data;
try {
data = SorobanTransactionData.fromXdrBase64(sorobanData);
} catch (IOException e) {
throw new IllegalArgumentException("Invalid Soroban data: " + sorobanData, e);
}
return setSorobanData(data);
this.mSorobanData = new SorobanDataBuilder(sorobanData).build();
return this;
}
}
Loading