Skip to content

Commit 579d41b

Browse files
authored
Merge pull request #15 from bloxbean/ccl_evaluator
Implementation for CCL's TransactionEvaluator
2 parents 550b499 + 59d05d9 commit 579d41b

File tree

5 files changed

+182
-126
lines changed

5 files changed

+182
-126
lines changed

.github/workflows/intergration-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ jobs:
3131
run: chmod +x gradlew
3232
- name: Integration tests with Gradle
3333
run: ./gradlew clean integrationTest --stacktrace
34+
env:
35+
BF_PROJECT_ID: ${{ secrets.BF_PROJECT_ID }}

build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies {
3030

3131
testImplementation group: 'com.bloxbean.cardano', name: 'cardano-client-lib', version: '0.5.0-alpha.2'
3232
testImplementation group: 'com.bloxbean.cardano', name: 'cardano-client-backend-koios', version: '0.5.0-alpha.2'
33+
testImplementation group: 'com.bloxbean.cardano', name: 'cardano-client-backend-blockfrost', version: '0.5.0-alpha.2'
3334

3435
testCompileOnly 'org.projectlombok:lombok:1.18.26'
3536
testAnnotationProcessor 'org.projectlombok:lombok:1.18.26'
@@ -71,6 +72,11 @@ artifacts {
7172
archives javadocJar
7273
}
7374

75+
integrationTest {
76+
description = 'Set system properties for integration tests'
77+
systemProperty('BF_PROJECT_ID', findProperty("BF_PROJECT_ID"))
78+
}
79+
7480
//create a single Jar with all dependencies
7581
task fatJar(type: Jar) {
7682
manifest {

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
group = com.bloxbean.cardano
22
artifactId = aiken-java-binding
3-
version = 0.0.7-SNAPSHOT
3+
version = 0.0.7
Lines changed: 54 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.bloxbean.cardano.aiken.tx.evaluator;
22

3+
import com.bloxbean.cardano.aiken.AikenTransactionEvaluator;
34
import com.bloxbean.cardano.client.account.Account;
45
import com.bloxbean.cardano.client.address.AddressProvider;
56
import com.bloxbean.cardano.client.api.ProtocolParamsSupplier;
@@ -11,34 +12,27 @@
1112
import com.bloxbean.cardano.client.backend.api.BackendService;
1213
import com.bloxbean.cardano.client.backend.api.DefaultProtocolParamsSupplier;
1314
import com.bloxbean.cardano.client.backend.api.DefaultUtxoSupplier;
14-
import com.bloxbean.cardano.client.backend.koios.KoiosBackendService;
15-
import com.bloxbean.cardano.client.backend.model.TransactionContent;
16-
import com.bloxbean.cardano.client.coinselection.UtxoSelectionStrategy;
17-
import com.bloxbean.cardano.client.coinselection.impl.LargestFirstUtxoSelectionStrategy;
15+
import com.bloxbean.cardano.client.backend.blockfrost.common.Constants;
16+
import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService;
1817
import com.bloxbean.cardano.client.common.CardanoConstants;
1918
import com.bloxbean.cardano.client.common.model.Networks;
2019
import com.bloxbean.cardano.client.exception.CborSerializationException;
21-
import com.bloxbean.cardano.client.function.Output;
22-
import com.bloxbean.cardano.client.function.TxBuilder;
23-
import com.bloxbean.cardano.client.function.TxBuilderContext;
24-
import com.bloxbean.cardano.client.function.TxSigner;
25-
import com.bloxbean.cardano.client.function.helper.*;
26-
import com.bloxbean.cardano.client.function.helper.model.ScriptCallContext;
27-
import com.bloxbean.cardano.client.plutus.spec.*;
28-
import com.bloxbean.cardano.client.transaction.spec.*;
29-
import com.bloxbean.cardano.client.transaction.util.CostModelUtil;
20+
import com.bloxbean.cardano.client.function.helper.ScriptUtxoFinders;
21+
import com.bloxbean.cardano.client.function.helper.SignerProviders;
22+
import com.bloxbean.cardano.client.plutus.spec.BigIntPlutusData;
23+
import com.bloxbean.cardano.client.plutus.spec.PlutusData;
24+
import com.bloxbean.cardano.client.plutus.spec.PlutusV2Script;
25+
import com.bloxbean.cardano.client.quicktx.QuickTxBuilder;
26+
import com.bloxbean.cardano.client.quicktx.ScriptTx;
27+
import com.bloxbean.cardano.client.quicktx.Tx;
28+
import com.bloxbean.cardano.client.transaction.spec.TransactionInput;
3029
import com.bloxbean.cardano.client.util.JsonUtil;
31-
import org.assertj.core.util.Lists;
3230
import org.junit.jupiter.api.Assertions;
3331
import org.junit.jupiter.api.Test;
3432

3533
import java.math.BigInteger;
36-
import java.util.Collections;
37-
import java.util.HashSet;
3834
import java.util.List;
39-
import java.util.Set;
40-
41-
import static com.bloxbean.cardano.client.common.ADAConversionUtil.adaToLovelace;
35+
import java.util.Optional;
4236

4337
//The caller has to guess the sum of 0..datum_value to claim the locked fund.
4438
public class GuessSumContractIntegrationTest {
@@ -49,8 +43,8 @@ public class GuessSumContractIntegrationTest {
4943

5044
String senderAddress = sender.baseAddress();
5145

52-
BackendService backendService = new KoiosBackendService(com.bloxbean.cardano.client.backend.koios.Constants.KOIOS_PREPROD_URL);
53-
46+
// BackendService backendService = new KoiosBackendService(com.bloxbean.cardano.client.backend.koios.Constants.KOIOS_PREPROD_URL);
47+
BackendService backendService = new BFBackendService(Constants.BLOCKFROST_PREPROD_URL, System.getenv("BF_PROJECT_ID"));
5448
private UtxoSupplier utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService());
5549

5650
private ProtocolParamsSupplier protocolParamsSupplier = new DefaultProtocolParamsSupplier(backendService.getEpochService());
@@ -71,133 +65,68 @@ public void invokeContract() throws Exception {
7165
PlutusData datum = BigIntPlutusData.of(8);
7266

7367
lockFundWithInlineDatum(scriptAddress, datum); //CIP-32
68+
Thread.sleep(4000); //To avoid retrieving utxo which was just spent
7469

7570
//3. Claim fund by guessing the sum
7671
//Get script utxo
77-
7872
Utxo scriptUtxo = ScriptUtxoFinders.findFirstByDatumHashUsingDatum(utxoSupplier, scriptAddress, datum).orElseThrow();
7973
BigInteger claimAmount = scriptUtxo
8074
.getAmount().stream().filter(amount -> CardanoConstants.LOVELACE.equals(amount.getUnit()))
8175
.findFirst()
8276
.orElseThrow().getQuantity();
8377

84-
Output output = Output.builder()
85-
.address(senderAddress)
86-
.assetName(CardanoConstants.LOVELACE)
87-
.qty(claimAmount)
88-
.build();
89-
90-
ScriptCallContext scriptCallContext = ScriptCallContext
91-
.builder()
92-
.script(sumScript)
93-
.exUnits(ExUnits.builder() //Exact exUnits will be calculated later
94-
.mem(BigInteger.valueOf(0))
95-
.steps(BigInteger.valueOf(0))
96-
.build())
97-
.redeemer(BigIntPlutusData.of(36))
98-
.redeemerTag(RedeemerTag.Spend).build();
99-
100-
// Find collaterals
101-
UtxoSelectionStrategy utxoSelectionStrategy = new LargestFirstUtxoSelectionStrategy(utxoSupplier);
102-
103-
Set<Utxo> collateralUtxos =
104-
utxoSelectionStrategy.select(senderAddress, new Amount(CardanoConstants.LOVELACE, adaToLovelace(5)), Collections.emptySet());
105-
10678
//Get reference input
10779
TransactionInput refInput = new TransactionInput("5a47b9a4276362000566ac5e58c18f315440a78a8cb0a8d1fe066e0012bcfbab", 0);
108-
//Hardcoding referenceInputUtxo for now. This will be removed after https://github.com/bloxbean/cardano-client-lib/pull/241
109-
Utxo referenceInputUtxo = Utxo.builder()
110-
.address("addr_test1wzcppsyg36f65jydjsd6fqu3xm7whxu6nmp3pftn9xfgd4ckah4da")
111-
.txHash(refInput.getTransactionId())
112-
.outputIndex(refInput.getIndex())
113-
.amount(List.of(new Amount(CardanoConstants.LOVELACE, adaToLovelace(9.34408))))
114-
.referenceScriptHash("b010c0888e93aa488d941ba4839136fceb9b9a9ec310a573299286d7")
115-
.build();
116-
117-
TxBuilder contractTxBuilder = output.outputBuilder()
118-
.buildInputs(InputBuilders.createFromUtxos(List.of(scriptUtxo)))
119-
.andThen(InputBuilders.referenceInputsFrom(List.of(refInput)))
120-
.andThen(CollateralBuilders.collateralOutputs(senderAddress, Lists.newArrayList(collateralUtxos))) //CIP-40
121-
.andThen(ScriptCallContextProviders.createFromScriptCallContext(scriptCallContext))
122-
.andThen((context, txn) -> {
123-
CostMdls costMdls = new CostMdls();
124-
costMdls.add(CostModelUtil.PlutusV2CostModel);
125-
126-
//Fix required in cardano-client-lib to also include reference input utxo in the context
127-
Set<Utxo> utxos = new HashSet<>(context.getUtxos());
128-
utxos.add(referenceInputUtxo);
129-
130-
//Evaluate ExUnits
131-
SlotConfig slotConfig = new SlotConfig(1000, 0, 100);
132-
InitialBudgetConfig initialBudgetConfig = new InitialBudgetConfig(14000000L, 10000000000L);
133-
TxEvaluator txEvaluator = new TxEvaluator(slotConfig, initialBudgetConfig);
134-
List<Redeemer> redeemerList = txEvaluator.evaluateTx(txn, utxos, costMdls);
135-
txn.getWitnessSet().getRedeemers().get(0).setExUnits(redeemerList.get(0).getExUnits());
136-
137-
System.out.println("ExUnits evaluation From Aiken:" + redeemerList);
138-
80+
ScriptTx tx = new ScriptTx()
81+
.payToAddress(senderAddress, Amount.lovelace(claimAmount))
82+
.collectFrom(scriptUtxo, BigIntPlutusData.of(36))
83+
.readFrom(refInput.getTransactionId(), refInput.getIndex())
84+
.attachSpendingValidator(sumScript);
85+
86+
QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService);
87+
Result<String> result = quickTxBuilder.compose(tx)
88+
.feePayer(senderAddress)
89+
.withSigner(SignerProviders.signerFrom(sender))
90+
.withTxInspector(transaction -> System.out.println(JsonUtil.getPrettyJson(transaction)))
91+
.withTxEvaluator(new AikenTransactionEvaluator(backendService))
92+
.postBalanceTx((context, txn) -> {
93+
//Remove all script witness sets as we are using reference inptuts
13994
txn.getWitnessSet().getPlutusV2Scripts().clear();
140-
})
141-
.andThen(BalanceTxBuilders.balanceTx(senderAddress, 2));
142-
143-
TxBuilderContext txBuilderContext = TxBuilderContext.init(utxoSupplier, protocolParamsSupplier);
144-
145-
//Tx Build and Submit
146-
TxSigner signer = SignerProviders.signerFrom(sender);
147-
148-
Transaction signedTx = txBuilderContext
149-
.buildAndSign(contractTxBuilder, signer);
150-
151-
Result<String> result = backendService.getTransactionService().submitTransaction(signedTx.serialize());
95+
}).completeAndWait(System.out::println);
15296

15397
System.out.println("Unlock Tx: " + result);
154-
15598
Assertions.assertTrue(result.isSuccessful());
156-
waitForTransaction(result);
15799
}
158100

159101
private void lockFundWithInlineDatum(String scriptAddress, PlutusData datum) throws ApiException, CborSerializationException {
160-
Output lockOutput = Output.builder()
161-
.address(scriptAddress)
162-
.assetName(CardanoConstants.LOVELACE)
163-
.qty(adaToLovelace(4))
164-
.datum(datum)
165-
.inlineDatum(true).build();
166-
167-
TxBuilder lockFundTxBuilder = lockOutput.outputBuilder()
168-
.buildInputs(InputBuilders.createFromSender(senderAddress, senderAddress))
169-
.andThen(BalanceTxBuilders.balanceTx(senderAddress, 1));
102+
Tx tx = new Tx()
103+
.payToContract(scriptAddress, Amount.ada(4.0), datum)
104+
.from(senderAddress);
170105

171-
Transaction signedTx = TxBuilderContext.init(utxoSupplier, protocolParamsSupplier)
172-
.buildAndSign(lockFundTxBuilder, SignerProviders.signerFrom(sender));
106+
QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService);
107+
Result<String> result = quickTxBuilder.compose(tx)
108+
.withSigner(SignerProviders.signerFrom(sender))
109+
.completeAndWait(System.out::println);
173110

174-
Result<String> result = backendService.getTransactionService().submitTransaction(signedTx.serialize());
175-
System.out.println("Lock Tx: " + result);
111+
System.out.println("Lock Tx: " + result);
112+
Assertions.assertTrue(result.isSuccessful());
176113

177-
Assertions.assertTrue(result.isSuccessful());
178-
waitForTransaction(result);
114+
checkIfUtxoAvailable(result.getValue(), scriptAddress);
179115
}
180116

181-
private void waitForTransaction(Result<String> result) {
182-
try {
183-
if (result.isSuccessful()) { //Wait for transaction to be mined
184-
int count = 0;
185-
while (count < 60) {
186-
Result<TransactionContent> txnResult = backendService.getTransactionService().getTransaction(result.getValue());
187-
if (txnResult.isSuccessful()) {
188-
System.out.println(JsonUtil.getPrettyJson(txnResult.getValue()));
189-
break;
190-
} else {
191-
System.out.println("Waiting for transaction to be mined ....");
192-
}
193-
194-
count++;
195-
Thread.sleep(2000);
196-
}
197-
}
198-
} catch (Exception e) {
199-
e.printStackTrace();
117+
protected void checkIfUtxoAvailable(String txHash, String address) {
118+
Optional<Utxo> utxo = Optional.empty();
119+
int count = 0;
120+
while (utxo.isEmpty()) {
121+
if (count++ >= 20)
122+
break;
123+
List<Utxo> utxos = new DefaultUtxoSupplier(backendService.getUtxoService()).getAll(address);
124+
utxo = utxos.stream().filter(u -> u.getTxHash().equals(txHash))
125+
.findFirst();
126+
System.out.println("Try to get new output... txhash: " + txHash);
127+
try {
128+
Thread.sleep(1000);
129+
} catch (Exception e) {}
200130
}
201131
}
202-
203132
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.bloxbean.cardano.aiken;
2+
3+
import com.bloxbean.cardano.aiken.tx.evaluator.TxEvaluator;
4+
import com.bloxbean.cardano.client.api.ProtocolParamsSupplier;
5+
import com.bloxbean.cardano.client.api.TransactionEvaluator;
6+
import com.bloxbean.cardano.client.api.UtxoSupplier;
7+
import com.bloxbean.cardano.client.api.exception.ApiException;
8+
import com.bloxbean.cardano.client.api.model.EvaluationResult;
9+
import com.bloxbean.cardano.client.api.model.ProtocolParams;
10+
import com.bloxbean.cardano.client.api.model.Result;
11+
import com.bloxbean.cardano.client.api.model.Utxo;
12+
import com.bloxbean.cardano.client.api.util.CostModelUtil;
13+
import com.bloxbean.cardano.client.backend.api.BackendService;
14+
import com.bloxbean.cardano.client.backend.api.DefaultProtocolParamsSupplier;
15+
import com.bloxbean.cardano.client.backend.api.DefaultUtxoSupplier;
16+
import com.bloxbean.cardano.client.plutus.spec.CostMdls;
17+
import com.bloxbean.cardano.client.plutus.spec.CostModel;
18+
import com.bloxbean.cardano.client.plutus.spec.Language;
19+
import com.bloxbean.cardano.client.plutus.spec.Redeemer;
20+
import com.bloxbean.cardano.client.transaction.spec.Transaction;
21+
import com.bloxbean.cardano.client.transaction.spec.TransactionInput;
22+
import com.bloxbean.cardano.client.transaction.spec.TransactionWitnessSet;
23+
import com.bloxbean.cardano.client.util.JsonUtil;
24+
import lombok.NonNull;
25+
26+
import java.util.*;
27+
import java.util.stream.Collectors;
28+
29+
import static com.bloxbean.cardano.client.plutus.spec.Language.PLUTUS_V1;
30+
import static com.bloxbean.cardano.client.plutus.spec.Language.PLUTUS_V2;
31+
32+
/**
33+
* Implements TransactionEvaluator to evaluate a transaction to get script costs using Aiken evaluator.
34+
* This is a wrapper around TxEvaluator.
35+
*/
36+
public class AikenTransactionEvaluator implements TransactionEvaluator {
37+
private UtxoSupplier utxoSupplier;
38+
private ProtocolParamsSupplier protocolParamsSupplier;
39+
40+
/**
41+
* Constructor
42+
*
43+
* @param backendService Backend service
44+
*/
45+
public AikenTransactionEvaluator(@NonNull BackendService backendService) {
46+
this.utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService());
47+
this.protocolParamsSupplier = new DefaultProtocolParamsSupplier(backendService.getEpochService());
48+
}
49+
50+
/**
51+
* Constructor
52+
*
53+
* @param utxoSupplier Utxo supplier
54+
* @param protocolParamsSupplier Protocol params supplier
55+
*/
56+
public AikenTransactionEvaluator(UtxoSupplier utxoSupplier, ProtocolParamsSupplier protocolParamsSupplier) {
57+
this.utxoSupplier = utxoSupplier;
58+
this.protocolParamsSupplier = protocolParamsSupplier;
59+
}
60+
61+
@Override
62+
public Result<List<EvaluationResult>> evaluateTx(byte[] cbor, Set<Utxo> inputUtxos) throws ApiException {
63+
try {
64+
Transaction transaction = Transaction.deserialize(cbor);
65+
66+
Set<Utxo> utxos = new HashSet<>();
67+
//inputs
68+
for (TransactionInput input : transaction.getBody().getInputs()) {
69+
Utxo utxo = utxoSupplier.getTxOutput(input.getTransactionId(), input.getIndex())
70+
.get();
71+
utxos.add(utxo);
72+
}
73+
74+
//reference inputs
75+
for (TransactionInput input : transaction.getBody().getReferenceInputs()) {
76+
Utxo utxo = utxoSupplier.getTxOutput(input.getTransactionId(), input.getIndex())
77+
.get();
78+
utxos.add(utxo);
79+
}
80+
81+
//The following initializations are required to avoid NPE in aiken-java-binding
82+
if (transaction.getWitnessSet() == null)
83+
transaction.setWitnessSet(new TransactionWitnessSet());
84+
85+
if (transaction.getWitnessSet().getPlutusV1Scripts() == null)
86+
transaction.getWitnessSet().setPlutusV1Scripts(new ArrayList<>());
87+
88+
if (transaction.getWitnessSet().getPlutusV2Scripts() == null)
89+
transaction.getWitnessSet().setPlutusV2Scripts(new ArrayList<>());
90+
91+
Language language =
92+
(transaction.getWitnessSet() != null && transaction.getWitnessSet().getPlutusV1Scripts() != null
93+
&& transaction.getWitnessSet().getPlutusV1Scripts().size() > 0) ?
94+
PLUTUS_V1 : PLUTUS_V2;
95+
ProtocolParams protocolParams = protocolParamsSupplier.getProtocolParams();
96+
Optional<CostModel> costModelOptional =
97+
CostModelUtil.getCostModelFromProtocolParams(protocolParams, language);
98+
if (!costModelOptional.isPresent())
99+
throw new ApiException("Cost model not found for language: " + language);
100+
101+
CostMdls costMdls = new CostMdls();
102+
costMdls.add(costModelOptional.get());
103+
104+
TxEvaluator txEvaluator = new TxEvaluator();
105+
List<Redeemer> redeemers = txEvaluator.evaluateTx(transaction, utxos, costMdls);
106+
if (redeemers == null)
107+
return Result.success("Error evaluating transaction");
108+
109+
List<EvaluationResult> evaluationResults = redeemers.stream().map(redeemer -> EvaluationResult.builder()
110+
.redeemerTag(redeemer.getTag())
111+
.index(redeemer.getIndex().intValue())
112+
.exUnits(redeemer.getExUnits())
113+
.build()).collect(Collectors.toList());
114+
return Result.success(JsonUtil.getPrettyJson(evaluationResults)).withValue(evaluationResults);
115+
} catch (Exception e) {
116+
throw new ApiException("Error evaluating transaction", e);
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)