Skip to content

Commit

Permalink
feat: #49 Handle redeemers as map in Conway era
Browse files Browse the repository at this point in the history
  • Loading branch information
satran004 committed Feb 23, 2024
1 parent 81373fb commit 2375c39
Show file tree
Hide file tree
Showing 6 changed files with 409 additions and 48 deletions.
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

0 comments on commit 2375c39

Please sign in to comment.