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

Payment Transaction Improvements #59

Merged
merged 17 commits into from
Jan 11, 2022
Merged

Conversation

nemo83
Copy link
Contributor

@nemo83 nemo83 commented Jan 5, 2022

This PR aims to collate all payments towards the same address together.

  1. created plus (happy to get renamed to add or whatever makes sense) to the Asset, MultiAsset and Value classes
  2. created a new, createReceiverOutputsAndPopulateCostV2 method, this should/wil manage all the tx together and merge these going to the same address.

This should save in fees thanks to the creation of a single Value from the multiple Value objects creaated from a PAymentTransaction object.

It should also allow to solve the problem of this tx: https://cardanoscan.io/transaction/61f7f2d030b90344f9e3e6a0fabdfbedb13abc94ba33bc3453e6604fcdfd7c7f

Where I want to send a specific amount of ada (greated than the minAda) along with a native asset. In the tx above, despite having sent enough ada to the recipient address, I've still paid the min ada to send along with the native asset.

I haven't tested the PR yet, so please bear with me :-D

@satran004
Copy link
Member

Thanks a lot @nemo83 . Great enhancement. Just had a quick look. I will go through the PR in detail tomorrow.

"plus" sounds ok to me.

Yes, let's add some tests. You may also want to add an integration test.

Also, you may need to do a pull to update your branch. I just noticed "cardano-serialization-lib" sub module is still there in your fork. That dependency has already been removed.

@nemo83
Copy link
Contributor Author

nemo83 commented Jan 5, 2022 via email

@satran004
Copy link
Member

Thanks @nemo83

  1. I just noticed that the fee is quite high for the transaction that you had shared earlier. Can you please confirm if you are setting final fee only in one PaymentTransaction ?

In case of multiple PaymentTransactions, you need to set the fee only in one transaction. Otherwise n * fee will be paid.

Here's a test case for your reference :

//Calculate total fee for all 3 payment transactions and set in one of the payment transaction

  1. I think the problem is that fee at payment transaction object does not make sense anymore now, but should be an independent parameter.

Agreed. We can plan to deprecate that and remove it later.

  1. Also, I am thinking if someone has added two separate PaymetTransactions to one output address, one with ADA and other one with MultiAsset, should we always combine it ? Or, should we keep it optional (Like user may want to keep it separate for some other reason..) ?

We can explore if somehow we can pass a flag "consolidateOutputs" or "combineOutputValue" ... and then call this optimization method if the flag is set. One option, we can add this flag to "TransactionDetailsParams"

Let me know your thoughts.

@nemo83
Copy link
Contributor Author

nemo83 commented Jan 6, 2022

  1. No 🤦 I set the fee to each PaymentTransaction
  2. Awesome taht we are on the same page, I fear though the change might be quite extended. Maybe worth to issue a different PR for that.
  3. While I do get your point, I'm not sure that sending to different indexes of the same address makes sense. If you want to keep things separated, a user would send to two addresses of the same wallet. one with the ada alone and one with the asset + the min ada. Sending to same address in different utxos, as far as I can think, has no benefit, only extra costs. Am I missing something?

I still haven't figured out the Non Conservative issue in the PR 😭

@satran004
Copy link
Member

2. Awesome taht we are on the same page, I fear though the change might be quite extended. Maybe worth to issue a different PR for that.

Yes, a separate PR makes sense.

3. Sending to same address in different utxos, as far as I can think, has no benefit, only extra costs. Am I missing something?

You are right. I was overthinking. If someone wants more control, then low level serialization api is there. To keep it simple, high level api can behave in a certain way.

@@ -1,11 +1,28 @@
package com.bloxbean.cardano.client.util;

import java.util.Objects;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@satran004 what's the best way of fixing this?

I need equals and hashcode for the grouping to work properly. Should we introdue lombok instead?

Or leave it as is and create a test with the description so that if they get deleted the test will remind why we need them?

Copy link
Member

Choose a reason for hiding this comment

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

@nemo83 We can introduce Lombak if that solves your problem. We are using Lombok in other places.


if(minCost != null && totalLoveLace.compareTo(minCost) != 1) {
if (minCost != null && totalLoveLace.compareTo(minCost) != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

CompareToZero: The result of #compareTo or #compare should only be compared to 0. It is an implementation detail whether a given type returns strictly the values {-1, 0, +1} or others. (details)
(at-me in a reply with help or ignore)

* @param totalFee
* @param transactionOutputs
* @param senderMiscCostMap
* @param protocolParams
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

* @param detailsParams
* @param totalFee
* @param transactionOutputs
* @param senderMiscCostMap
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

* Deprecated, see createReceiverOutputsAndPopulateCostV2
* @param transaction
* @param detailsParams
* @param totalFee
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)


/**
* Deprecated, see createReceiverOutputsAndPopulateCostV2
* @param transaction
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

* @param transaction
* @param detailsParams
* @param totalFee
* @param transactionOutputs
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)


/**
* returns a new asset that is the sum of this and that (asset passed as parameter)
* @param that
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

/**
* Creates a new list of multi assets from those passed as parameters.
* Multi Assets with the same policy id will be aggregated together, and matching assets summed.
* @param multiAssets1
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

* Creates a new list of multi assets from those passed as parameters.
* Multi Assets with the same policy id will be aggregated together, and matching assets summed.
* @param multiAssets1
* @param multiAssets2
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)


/**
* Sums a Multi Asset to another. If an Asset is already present, sums the amounts.
* @param that
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

/**
* Sums a Multi Asset to another. If an Asset is already present, sums the amounts.
* @param that
* @return
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

@@ -65,4 +65,15 @@ public static Value deserialize(Array valueArray) {
return value;
}

/**
* Sums arbitrary complex values.
* @param that
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

/**
* Sums arbitrary complex values.
* @param that
* @return
Copy link
Contributor

Choose a reason for hiding this comment

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

EmptyBlockTag: A block tag (@param, @return, @throws, @deprecated) has an empty description. Block tags without descriptions don't add much value for future readers of the code; consider removing the tag entirely or adding a description. (details)
(at-me in a reply with help or ignore)

public T _1;
public Z _2;

public Tuple(T _1, Z _2) {
this._1 = _1;
this._2 = _2;
}

@Override
public boolean equals(Object o) {
Copy link
Contributor

Choose a reason for hiding this comment

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

EqualsGetClass: Prefer instanceof to getClass when implementing Object#equals. (details)
(at-me in a reply with help or ignore)

* @param protocolParams
* @return
*/
@Deprecated
private BigInteger createReceiverOutputsAndPopulateCost(PaymentTransaction transaction, TransactionDetailsParams detailsParams, BigInteger totalFee,
Copy link
Contributor

Choose a reason for hiding this comment

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

UnusedMethod: Private method 'createReceiverOutputsAndPopulateCost' is never used. (details)
(at-me in a reply with help or ignore)

@@ -378,6 +382,82 @@ private void checkAndAddAdditionalUtxosIfMinCostIsNotMet(Map<String, Set<Utxo>>
return senderToUtxoMap;
}

private BigInteger createReceiverOutputsAndPopulateCostV2(List<PaymentTransaction> transactions, TransactionDetailsParams detailsParams, BigInteger totalFee,
Copy link
Contributor

Choose a reason for hiding this comment

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

UnusedVariable: The parameter 'detailsParams' is never read. (details)
(at-me in a reply with help or ignore)

@nemo83
Copy link
Contributor Author

nemo83 commented Jan 7, 2022

@satran004 struggling to run tests on Mac Book Pro x86_64.

I've run the . scripts/build-mac-x86_64.sh script.

I get this error on:

java.lang.UnsatisfiedLinkError: Error looking up function 'signExtended': dlsym(0x214644300, signExtended): symbol not found
	at com.sun.jna.Function.<init>(Function.java:252)
	at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:600)
	at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:576)
	at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:562)
	```

Any idea?

@satran004
Copy link
Member

I've run the . scripts/build-mac-x86_64.sh script.

I get this error on:

@nemo83 I tried on mac x86 with your repo. It's working for me though 3 tests are failing but different error.

Can you please delete native, rust/target folders under root folder and try to build rust module again ?

rm -rf native/ rust/target/

@nemo83
Copy link
Contributor Author

nemo83 commented Jan 8, 2022

@satran004 struggling to run tests on Mac Book Pro x86_64.

I've run the . scripts/build-mac-x86_64.sh script.

I get this error on:

java.lang.UnsatisfiedLinkError: Error looking up function 'signExtended': dlsym(0x214644300, signExtended): symbol not found
	at com.sun.jna.Function.<init>(Function.java:252)
	at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:600)
	at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:576)
	at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:562)

Any idea?

Thanks for the reply, you pointed me in the right direction, not a rust expert, but a cargo update fixed the problem for me.

After cleaning the folders you mentioned, cargo update and regenerated the lib, I was able to run again tests both from cli and IDE.

I also fixed all the tests, updating them in line with the rationale of the test, I've added comments in tests for clarity.

I hope I will find time for some additional testing and a couple of integration tests.

I'm overall happy with the outcome so far.

@@ -85,9 +88,9 @@ dependencies = [

[[package]]
name = "ed25519"
version = "1.3.0"
version = "1.2.0"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@satran004 this is what was bombing out. I needed a cargo update to generate the lib

@satran004
Copy link
Member

I hope I will find time for some additional testing and a couple of integration tests.

@nemo83 Great progress. Let me know once it's ready for merge.

Fyi, To run integration tests in IDE, you can set the project id in this class BFBaseTest.java.
When you are running all integration tests, occasionally you may find some test case failure which is due to network condition during that time. Just re-run the failed test case in that case.

@nemo83
Copy link
Contributor Author

nemo83 commented Jan 10, 2022

I hope I will find time for some additional testing and a couple of integration tests.

@nemo83 Great progress. Let me know once it's ready for merge.

Fyi, To run integration tests in IDE, you can set the project id in this class BFBaseTest.java. When you are running all integration tests, occasionally you may find some test case failure which is due to network condition during that time. Just re-run the failed test case in that case.

I've added just one IT as unit tests do already a pretty good job for the type of change I did.

I think the PR is ready to be merged if no objections or issue in the review?

@nemo83
Copy link
Contributor Author

nemo83 commented Jan 10, 2022

Also for some reasons the contract IT are failing on branch as well as aon master. so I geuss it's a problem with the tests?

existingMiscCost = existingMiscCost.add(costs);

// Update costs per (sender) base address
senderMiscCostMap.put(entry.getKey()._1.baseAddress(), existingMiscCost);
Copy link
Member

@satran004 satran004 Jan 10, 2022

Choose a reason for hiding this comment

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

@nemo83, thanks for the optimization.

Are we missing datumHash in case of transfer to a script address ? So if there is a datumHash set in PaymentTransaction, it should be added to the output.

I am just thinking what would happen when there are two PaymentTransactions (to the same script address), with two different datum hash. Should we also group PaymentTransaction based on datumHash (sender, receiver, datumHash) ?

Here's the link to existing code

if (transaction.getDatumHash() != null && !transaction.getDatumHash().isEmpty()) {

We can merge this PR and create an issue to track that as a separate PR.

Please let me know.

Copy link
Member

Choose a reason for hiding this comment

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

Or, ignore grouping in case there is datumhash. Probably a safer option.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a great catch. I guess I'm currently grouping for (sender, receiver), but because the datum hash is like another dimension of the transaction, I think I need to refactor the code to take that one in account too.

The line is: https://github.com/bloxbean/cardano-client-lib/pull/59/files#diff-5510ede2dd3e67b914b31850811f06b683736fb700062961aaca326d61493ff5R390

WDYT ?

Copy link
Member

Choose a reason for hiding this comment

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

@nemo83 Is it possible to ignore grouping when datumhash is there and just consider it as a separate output ? In case of script output, I am not sure if we can add this intelligence. It depends on the logic in validator script and output becomes non spendable based on the logic.

Probably safe to keep these output separate.

BTW, I just tried contract IT tests. Those are failing because of missing datumHash. The contract call transaction doesn't find any utxo with datum hash as the previous payment transaction ignores datum hash in output :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha, it makes sense tests were failing. Of course the DatumHash wasnt set.

I'm gonna propose some changes. I believe grouping can still be safely done.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right I've suggested some changed for the datum hash.

Pleasee check code here.

Although all the smart contracts tests are now passing, the following is constantly failing, in both master and this branch

TransactionHelperServiceIT. mintTokenWithNormalAccountKeyHash()

I will try to take a look at it tomorrow.

Copy link
Member

Choose a reason for hiding this comment

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

@nemo83 Please ignore the test failure for now. I think it is due to another check-in. I will fix it in the master after merge.

@satran004
Copy link
Member

satran004 commented Jan 10, 2022

Also for some reasons the contract IT are failing on branch as well as aon master. so I geuss it's a problem with the tests?

@nemo83 Yes. There is an issue with collateral amount for one of the contract. I need to increase the amount. I will fix that after merge.

Comment on lines +40 to +47
@Data
@AllArgsConstructor
private class PaymentTransactionGroupingKey {
private Account sender;
private String receiver;
private String datumHash;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This case class identifies the transaction grouping rationale. Basically in order to collate transactions in just one input:

  1. sender
  2. receiver
  3. datum (hash)
    need to be the same.

@satran004
Copy link
Member

@nemo83 Merging this PR. It looks good to me. Thanks a lot for this enhancement. Less fee is always better :)

@satran004 satran004 merged commit 92c9f29 into bloxbean:master Jan 11, 2022
@nemo83 nemo83 deleted the improvements branch January 11, 2022 14:36
@nemo83
Copy link
Contributor Author

nemo83 commented Jan 11, 2022

Thanks for the hard work. It's only my pleasure being able to contribute to a good opensource project. Even more if it's for cardano!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants