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

feat: #49 Handle redeemers as map in Conway era #52

Merged
merged 1 commit into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.bloxbean.cardano.client.exception.CborDeserializationException;
import com.bloxbean.cardano.client.plutus.spec.ExUnits;
import com.bloxbean.cardano.client.plutus.spec.RedeemerTag;
import com.bloxbean.cardano.yaci.core.exception.CborRuntimeException;
import com.bloxbean.cardano.yaci.core.util.CborSerializationUtil;
import com.bloxbean.cardano.yaci.core.util.HexUtil;
import lombok.*;
Expand All @@ -25,7 +26,7 @@ public class Redeemer {
private ExUnits exUnits;
private String cbor;

public static Redeemer deserialize(Array redeemerDI)
public static Redeemer deserializePreConway(Array redeemerDI)
throws CborDeserializationException, CborException {
List<DataItem> redeemerDIList = redeemerDI.getDataItems();
if (redeemerDIList == null || redeemerDIList.size() != 4) {
Expand All @@ -38,10 +39,47 @@ public static Redeemer deserialize(Array redeemerDI)
DataItem dataDI = redeemerDIList.get(2);
DataItem exUnitDI = redeemerDIList.get(3);

return getRedeemer(redeemerDI, (UnsignedInteger) tagDI, (UnsignedInteger) indexDI, dataDI, (Array) exUnitDI);
}

@SneakyThrows
public static Redeemer deserialize(Array keyDI, Array valueDI) { //Conway era
List<DataItem> keyDIList = keyDI.getDataItems();
List<DataItem> valueDIList = valueDI.getDataItems();

if (keyDIList == null || keyDIList.size() != 2) {
throw new CborRuntimeException(
"Redeemer deserialization error. Invalid no of DataItems in key");
}

if (valueDIList == null || valueDIList.size() != 2) {
throw new CborRuntimeException(
"Redeemer deserialization error. Invalid no of DataItems in value");
}

DataItem tagDI = keyDIList.get(0);
DataItem indexDI = keyDIList.get(1);
DataItem dataDI = valueDIList.get(0);
DataItem exUnitDI = valueDIList.get(1);

//TODO -- Cbor hex is a hack. It creates an array with all fields and then serializes it.
//This is added to make it work with the existing code in client applications. Need to fix this.
Array redeemerDI = new Array();
redeemerDI.add(tagDI);
redeemerDI.add(indexDI);
redeemerDI.add(dataDI);
redeemerDI.add(exUnitDI);

return getRedeemer(redeemerDI, (UnsignedInteger) tagDI, (UnsignedInteger) indexDI, dataDI, (Array) exUnitDI);

}

private static Redeemer getRedeemer(Array redeemerDI, UnsignedInteger tagDI, UnsignedInteger indexDI,
DataItem dataDI, Array exUnitDI) throws CborDeserializationException, CborException {
Redeemer redeemer = new Redeemer();

// Tag
int tagValue = ((UnsignedInteger) tagDI).getValue().intValue();
int tagValue = tagDI.getValue().intValue();
if (tagValue == 0) {
redeemer.setTag(RedeemerTag.Spend);
} else if (tagValue == 1) {
Expand All @@ -53,17 +91,16 @@ public static Redeemer deserialize(Array redeemerDI)
}

// Index
redeemer.setIndex(((UnsignedInteger) indexDI).getValue().intValue());
redeemer.setIndex(indexDI.getValue().intValue());

// Redeemer data
redeemer.setData(Datum.from(dataDI));

// Redeemer resource usage (ExUnits)
redeemer.setExUnits(ExUnits.deserialize((Array) exUnitDI));
redeemer.setExUnits(ExUnits.deserialize(exUnitDI));

//cbor
redeemer.setCbor(HexUtil.encodeHexString(CborSerializationUtil.serialize(redeemerDI, false)));

return redeemer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
import java.util.LinkedHashMap;
import java.util.List;

import static com.bloxbean.cardano.yaci.core.model.serializers.util.WitnessUtil.getArrayBytes;
import static com.bloxbean.cardano.yaci.core.model.serializers.util.WitnessUtil.getRedeemerFields;
import static com.bloxbean.cardano.yaci.core.model.serializers.util.WitnessUtil.*;
import static com.bloxbean.cardano.yaci.core.util.CborSerializationUtil.toInt;

@Slf4j
Expand Down Expand Up @@ -163,47 +162,99 @@ private void handleWitnessDatumRedeemer(long block, List<Witnesses> witnesses, b
}

/*
* redeemer = [ tag: redeemer_tag, index: uint, data: plutus_data, ex_units: ex_units ]
* redeemers =
* [ + [ tag: redeemer_tag, index: uint, data: plutus_data, ex_units: ex_units ] ]
* / { + [ tag: redeemer_tag, index: uint ] => [ data: plutus_data, ex_units: ex_units ] }
*/
List<Redeemer> redeemers = witness.getRedeemers();
if (redeemers != null && !redeemers.isEmpty()) {
var redeemerArrayBytes = getArrayBytes(witnessFields.get(BigInteger.valueOf(5L)));
if (redeemerArrayBytes.size() != redeemers.size()) {
log.error("block: {} redeemer does not have the same size", block);
} else {
for (int redeemerIdx = 0; redeemerIdx < redeemers.size(); redeemerIdx++) {
var redeemer = redeemers.get(redeemerIdx);
var redeemerBytes = redeemerArrayBytes.get(redeemerIdx);
var redeemerFields = getRedeemerFields(redeemerBytes);

if (redeemerFields.size() != 4) {
log.error("Missing redeemer fields. Expected size 4, but found {}", redeemerFields.size());
continue;
//throw new IllegalStateException("Redeemer missing field");
}

var actualRedeemerData = redeemerFields.get(2);
var redeemerData = redeemer.getData();
final var cbor = HexUtil.encodeHexString(actualRedeemerData);
final var hash = Datum.cborToHash(actualRedeemerData);
var redeemersBytes = witnessFields.get(BigInteger.valueOf(5L));

//Isolate the first 3 bits of the byte, which represent the "major type" in CBOR's encoding structure. (0xe0 = 11100000)
var majorType = MajorType.ofByte(redeemersBytes[0] & 0xe0);

if (majorType == MajorType.ARRAY) {
var redeemerArrayBytes = getArrayBytes(redeemersBytes);
if (redeemerArrayBytes.size() != redeemers.size()) {
log.error("block: {} redeemer does not have the same size", block);
} else {
for (int redeemerIdx = 0; redeemerIdx < redeemers.size(); redeemerIdx++) {
var redeemer = redeemers.get(redeemerIdx);
var redeemerBytes = redeemerArrayBytes.get(redeemerIdx);
var redeemerFields = getRedeemerFields(redeemerBytes);

if (redeemerFields.size() != 4) {
log.error("Missing redeemer fields. Expected size 4, but found {}", redeemerFields.size());
continue;
//throw new IllegalStateException("Redeemer missing field");
}

var actualRedeemerData = redeemerFields.get(2);
var redeemerData = redeemer.getData();
final var cbor = HexUtil.encodeHexString(actualRedeemerData);
final var hash = Datum.cborToHash(actualRedeemerData);

if (!redeemerData.getHash().equals(hash)) {
log.debug("Redeemer data hash mismatch : {} - {} - {}",
block, redeemerData.getHash(), hash);
}

var updatedRedeemerData = redeemerData.toBuilder()
.cbor(cbor)
.hash(hash)
.build();

var updatedRedeemer = redeemer.toBuilder()
.cbor(HexUtil.encodeHexString(redeemerBytes))
.data(updatedRedeemerData)
.build();

if (!redeemerData.getHash().equals(hash)) {
log.debug("Redeemer data hash mismatch : {} - {} - {}",
block, redeemerData.getHash(), hash);
redeemers.set(redeemerIdx, updatedRedeemer);
}
}
} else if (majorType == MajorType.MAP) {
List<Tuple<byte[], byte[]>> redeemerMapEntriesBytes = getRedeemerMapBytes(redeemersBytes);
if (redeemerMapEntriesBytes.size() != redeemers.size()) {
log.error("block: {} redeemer does not have the same size", block);
} else {
for (int redeemerIdx = 0; redeemerIdx < redeemers.size(); redeemerIdx++) {
var redeemer = redeemers.get(redeemerIdx);
var redeemerBytesKeyValueTuple = redeemerMapEntriesBytes.get(redeemerIdx);

//Get value field, as we only need redeemer data
var redeemerFields = getRedeemerFields(redeemerBytesKeyValueTuple._2);

if (redeemerFields.size() != 2) {
log.error("Missing redeemer fields in value. Expected size 2, but found {}", redeemerFields.size());
continue;
}

var updatedRedeemerData = redeemerData.toBuilder()
.cbor(cbor)
.hash(hash)
.build();
var actualRedeemerData = redeemerFields.get(1);
var redeemerData = redeemer.getData();
final var cbor = HexUtil.encodeHexString(actualRedeemerData);
final var hash = Datum.cborToHash(actualRedeemerData);

var updatedRedeemer = redeemer.toBuilder()
.cbor(HexUtil.encodeHexString(redeemerBytes))
.data(updatedRedeemerData)
.build();
if (!redeemerData.getHash().equals(hash)) {
log.debug("Redeemer data hash mismatch : {} - {} - {}",
block, redeemerData.getHash(), hash);
}

redeemers.set(redeemerIdx, updatedRedeemer);
var updatedRedeemerData = redeemerData.toBuilder()
.cbor(cbor)
.hash(hash)
.build();

var updatedRedeemer = redeemer.toBuilder()
//.cbor(HexUtil.encodeHexString(redeemerBytes))
.data(updatedRedeemerData)
.build();

redeemers.set(redeemerIdx, updatedRedeemer);
}
}
} else {
throw new IllegalStateException("Invalid major type for redeemer list bytes : " + majorType);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public Witnesses deserializeDI(DataItem di) {
DataItem bootstrapWitnessArray = witnessMap.get(new UnsignedInteger(2));
DataItem plutusScriptArray = witnessMap.get(new UnsignedInteger(3));
DataItem plutusDataArray = witnessMap.get(new UnsignedInteger(4));
DataItem redeemerArray = witnessMap.get(new UnsignedInteger(5));
DataItem redeemersDI = witnessMap.get(new UnsignedInteger(5));
DataItem plutusV2ScriptArray = witnessMap.get(new UnsignedInteger(6));
DataItem plutusV3ScriptArray = witnessMap.get(new UnsignedInteger(7));

Expand Down Expand Up @@ -111,14 +111,21 @@ public Witnesses deserializeDI(DataItem di) {

//redeemers
List<Redeemer> redeemerList = new ArrayList<>();
if (redeemerArray != null) {
List<DataItem> redeemerDIList = ((Array) redeemerArray).getDataItems();
for (DataItem redeemerDI : redeemerDIList) {
if (redeemerDI == Special.BREAK) continue;
//Redeemer redeemer = new Redeemer(HexUtil.encodeHexString(CborSerializationUtil.serialize(redeemerDI, false)));
Redeemer redeemer = Redeemer.deserialize((Array) redeemerDI);
redeemerList.add(redeemer);
// redeemers.add(Redeemer.deserialize((Array) redeemerDI)); //TODO -- convert redeemer to json
if (redeemersDI != null) {
if (redeemersDI instanceof Array) {
List<DataItem> redeemerDIList = ((Array) redeemersDI).getDataItems();
for (DataItem redeemerDI : redeemerDIList) {
if (redeemerDI == Special.BREAK) continue;
Redeemer redeemer = Redeemer.deserializePreConway((Array) redeemerDI);
redeemerList.add(redeemer);
}
} else if (redeemersDI instanceof Map) { //conway era
var redeemersMap = (Map) redeemersDI;
for (DataItem key : redeemersMap.getKeys()) {
var valueDI = redeemersMap.get(key);
Redeemer redeemer = Redeemer.deserialize((Array)key, (Array) valueDI);
redeemerList.add(redeemer);
}
}
}

Expand Down Expand Up @@ -152,7 +159,6 @@ public Witnesses deserializeDI(DataItem di) {

PlutusScript plutusScript = new PlutusScript(String.valueOf(3), scriptCborHex);
plutusV3Scripts.add(plutusScript);

}
} catch (Exception e) {
throw new CborRuntimeException("Plutus V3 script deserialization failed", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import co.nstant.in.cbor.model.AdditionalInformation;
import co.nstant.in.cbor.model.Special;
import co.nstant.in.cbor.model.UnsignedInteger;
import com.bloxbean.cardano.yaci.core.util.Tuple;

import java.io.ByteArrayInputStream;
import java.math.BigInteger;
Expand Down Expand Up @@ -87,6 +88,37 @@ public static java.util.Map<BigInteger, byte[]> getWitnessFields(byte[] witnessB
return witnessMap;
}

//Conway era
public static List<Tuple<byte[], byte[]>> getRedeemerMapBytes(byte[] redeemerBytes)
throws CborException {
var redeemerList = new ArrayList<Tuple<byte[], byte[]>>();

ByteArrayInputStream stream = new ByteArrayInputStream(redeemerBytes);
CborDecoder decoder = new CborDecoder(stream);

stream.read();

while (stream.available() > 0) {
int keyStartFrom = redeemerBytes.length - stream.available();
int previousAvailable = stream.available();
var keyDI = decoder.decodeNext();
int currentAvailable = stream.available();
final byte[] keyBytes = new byte[previousAvailable - currentAvailable];
System.arraycopy(redeemerBytes, keyStartFrom, keyBytes, 0, keyBytes.length);

int valueStartFrom = redeemerBytes.length - stream.available();
previousAvailable = stream.available();
var valueDI = decoder.decodeNext();
currentAvailable = stream.available();
final byte[] valueBytes = new byte[previousAvailable - currentAvailable];
System.arraycopy(redeemerBytes, valueStartFrom, valueBytes, 0, valueBytes.length);

redeemerList.add(new Tuple<>(keyBytes, valueBytes));
}

return redeemerList;
}

/**
* Get CDDL array elements in bytes
* @param bytes CDDL array bytes
Expand Down
Loading
Loading