Skip to content
Open
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
137 changes: 137 additions & 0 deletions cardano/mainnet/00_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,143 @@ queryLight_UTXO() { #${1} = address to query
unset utxoRet response responseCode responseJSON addr error errorcnt

}
# Return authoritative reference-script sizes for an exact JSON array of transaction inputs.
queryReferenceScriptSizes() {
local requested="${1}"
local ids id result="{}" response responseJSON responseCode size sizeJSON rows
local -a queryArgs

if ! ids=$(jq -ce 'if type != "array" or any(.[]; type != "string" or (test("^[0-9a-fA-F]{64}#[0-9]+$") | not)) or length != (unique | length) then error("invalid input list") else . end' <<< "${requested}" 2>/dev/null); then
echo "ERROR - Invalid reference-script input list." >&2
return 1
fi
if [[ "$(jq length <<< "${ids}")" -eq 0 ]]; then printf '{}'; return 0; fi

case ${workMode} in
"online")
queryArgs=()
while IFS= read -r id; do queryArgs+=(--tx-in "${id}"); done < <(jq -r '.[]' <<< "${ids}")
response=$(${cardanocli} ${cliEra} query utxo --output-json "${queryArgs[@]}" 2>&1)
if [[ $? -ne 0 ]] || ! jq -e --argjson ids "${ids}" '
. as $utxos | type == "object" and
(keys | sort) == ($ids | sort) and
all($ids[]; . as $id | $utxos[$id] | type == "object" and has("referenceScript"))
' >/dev/null 2>&1 <<< "${response}"; then
echo "ERROR - Could not obtain complete reference-script information from cardano-cli." >&2
return 1
fi
while IFS= read -r id; do
if [[ "$(jq -r --arg id "${id}" '.[$id].referenceScript == null' <<< "${response}")" == "true" ]]; then
result=$(jq -c --arg id "${id}" '. + {($id): null}' <<< "${result}")
else
size=$(${cardanocli} ${cliEra} query ref-script-size --output-json --tx-in "${id}" 2>&1)
if [[ $? -ne 0 ]] || ! size=$(jq -er '.refInputScriptSize | select(type == "number" and . >= 0 and floor == .)' <<< "${size}" 2>/dev/null); then
echo "ERROR - Could not determine the reference-script size for '${id}'." >&2
return 1
fi
result=$(jq -c --arg id "${id}" --argjson size "${size}" '. + {($id): $size}' <<< "${result}")
fi
done < <(jq -r '.[]' <<< "${ids}")
;;

"light")
local errorcnt=0 error=-1
while [[ ${errorcnt} -lt 5 && ${error} -ne 0 ]]; do
error=0
response=$(curl -sL -m 120 -X POST -w "---spo-scripts---%{http_code}" "${koiosAPI}/utxo_info?select=tx_hash,tx_index,is_spent,reference_script->hash,reference_script->size" -H "${koiosAuthorizationHeader}" -H "Accept: application/json" -H "Content-Type: application/json" -d "$(jq -cn --argjson ids "${ids}" '{_utxo_refs:$ids,_extended:true}')" 2>/dev/null)
if [[ $? -ne 0 ]]; then error=1; fi
errorcnt=$((errorcnt + 1))
done
if [[ ${error} -ne 0 || ! "${response}" =~ (.*)---spo-scripts---([0-9]+)$ ]]; then
echo "ERROR - Query of the Koios-API via curl failed." >&2
return 1
fi
responseJSON="${BASH_REMATCH[1]}"; responseCode="${BASH_REMATCH[2]}"
if [[ "${responseCode}" != "200" ]]; then echo "ERROR - Koios HTTP response code: ${responseCode}" >&2; return 1; fi
if ! rows=$(jq -ce --argjson ids "${ids}" '
if type != "array" then error("not an array") else
map(. + {id:(.tx_hash + "#" + (.tx_index | tostring))}) |
if length != ($ids | length) or (map(.id) | sort) != ($ids | sort) or (map(.id) | unique | length) != length or
any(.[]; .is_spent != false or (has("hash") | not) or (has("size") | not) or
(((.hash == null and .size == null) or
((.hash | type) == "string" and (.hash | test("^[0-9a-fA-F]{56}$")) and
(.size | type) == "number" and .size >= 0 and (.size | floor) == .size)) | not))
then error("incomplete response") else . end
end
' <<< "${responseJSON}" 2>/dev/null); then
echo "ERROR - Koios returned incomplete or invalid reference-script information." >&2
return 1
fi
result=$(jq -c 'map({key:.id, value:(if .hash == null then null else .size end)}) | from_entries' <<< "${rows}")
;;

"offline")
if ! jq -e 'type == "object"' >/dev/null 2>&1 <<< "${offlineJSON:-}"; then readOfflineFile; fi
while IFS= read -r id; do
if ! sizeJSON=$(jq -ce --arg id "${id}" '
[.address[]? | select((.utxoJSON | type) == "object" and (.utxoJSON | has($id))) |
if (.referenceScriptSizes | type) == "object" and (.referenceScriptSizes | has($id))
then .referenceScriptSizes[$id] else "__missing__" end] as $matches |
if ($matches | length) != 1 or $matches[0] == "__missing__" or
(($matches[0] == null) | not) and (($matches[0] | type) != "number" or $matches[0] < 0 or ($matches[0] | floor) != $matches[0])
then error("missing or conflicting metadata") else [$matches[0]] end
' <<< "${offlineJSON}" 2>/dev/null); then
echo "Reference-script information is missing; refresh the payment address with 01_workOffline.sh add on the online/light machine and transfer the capture again." >&2
return 1
fi
size=$(jq -c '.[0]' <<< "${sizeJSON}")
result=$(jq -c --arg id "${id}" --argjson size "${size}" '. + {($id): $size}' <<< "${result}")
done < <(jq -r '.[]' <<< "${ids}")
;;

*) echo "ERROR - Unknown workMode '${workMode}'." >&2; return 1;;
esac

printf '%s' "${result}"
}

# Inspect the real transaction body, set referenceScriptSize, and warn about script-bearing inputs.
prepareReferenceScriptSpend() {
local txFile="${1}" view ids sizes feeIds spending collateralOnly
unset referenceScriptSize

view=$(${cardanocli} debug transaction view --output-json --tx-file "${txFile}" 2>&1)
if [[ $? -ne 0 ]] || ! jq -e '
type == "object" and
has("inputs") and has("reference inputs") and has("collateral inputs") and
([.inputs, ."reference inputs", ."collateral inputs"] | all(.[]; type == "array" and all(.[]; type == "string")))
' >/dev/null 2>&1 <<< "${view}"; then
echo "ERROR - Could not inspect transaction inputs in '${txFile}'." >&2
return 1
fi

ids=$(jq -c '[.inputs[], ."reference inputs"[], ."collateral inputs"[]] | unique' <<< "${view}")
sizes=$(queryReferenceScriptSizes "${ids}") || return 1
feeIds=$(jq -c '[.inputs[], ."reference inputs"[]] | unique' <<< "${view}")
if ! referenceScriptSize=$(jq -er --argjson ids "${feeIds}" --argjson sizes "${sizes}" '$ids | map($sizes[.] // 0) | add // 0 | select(type == "number" and . >= 0 and floor == .)' <<< '{}'); then
unset referenceScriptSize
echo "ERROR - Invalid reference-script fee total." >&2
return 1
fi

spending=$(jq -r --argjson sizes "${sizes}" '.inputs[] | select($sizes[.] != null) | "\(.) (\($sizes[.]) bytes)"' <<< "${view}")
if [[ -n "${spending}" ]]; then
echo "WARNING: This transaction spends reference-script UTxOs. Their existing reference locations will disappear; sending ADA back does not preserve the scripts. Applications using these UTxOs may stop working." >&2
while IFS= read -r id; do echo " ${id}" >&2; done <<< "${spending}"
fi

collateralOnly=$(jq -r --argjson sizes "${sizes}" '
([.inputs[], ."reference inputs"[]] | unique) as $fee |
."collateral inputs"[] as $id | select(($fee | index($id)) == null and $sizes[$id] != null) |
"\($id) (\($sizes[$id]) bytes)"
' <<< "${view}")
if [[ -n "${collateralOnly}" ]]; then
echo "WARNING: These collateral reference-script UTxOs may be consumed if phase-2 validation fails:" >&2
while IFS= read -r id; do echo " ${id}" >&2; done <<< "${collateralOnly}"
fi
return 0
}

#-------------------------------------------------------


Expand Down
8 changes: 6 additions & 2 deletions cardano/mainnet/01_claimRewards.sh
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,10 @@ if [[ ${rxcnt} == 1 ]]; then
checkError "$?"; if [ $? -ne 0 ]; then exit $?; fi
fi

prepareReferenceScriptSpend "$txBodyFile" || exit 1

#calculate the transaction fee. new parameters since cardano-cli 8.21.0
fee=$(${cardanocli} ${cliEra} transaction calculate-min-fee --output-text --tx-body-file ${txBodyFile} --protocol-params-file <(echo ${protocolParametersJSON}) --witness-count 2 --reference-script-size 0 2> /dev/stdout)
fee=$(${cardanocli} ${cliEra} transaction calculate-min-fee --output-text --tx-body-file ${txBodyFile} --protocol-params-file <(echo ${protocolParametersJSON}) --witness-count 2 --reference-script-size "${referenceScriptSize}" 2> /dev/stdout)
if [ $? -ne 0 ]; then echo -e "\n\e[35m${fee}\e[0m\n"; exit 1; fi
fee=${fee%% *} #only get the first part of 'xxxxxx Lovelaces'

Expand Down Expand Up @@ -756,14 +758,16 @@ if [ "${ENV_SKIP_PROMPT}" == "YES" ] || ask "\n\e[33mDoes this look good for you
sendFromAddr: \"${sendFromAddr}\",
toAddr: \"${toAddr}\",
sendToAddr: \"${sendToAddr}\",
referenceScriptSize: ${referenceScriptSize},
txJSON: ${txFileJSON} } ]" <<< ${offlineJSON})
#Write the new offileFile content
offlineJSON=$( jq ".history += [ { date: \"$(date -R)\", action: \"signed rewards withdrawal from '${stakeAddr}' to '${toAddr}', payment via '${fromAddr}'\" } ]" <<< ${offlineJSON})
offlineJSON=$( jq ".general += {offlineCLI: \"${versionCLI}\" }" <<< ${offlineJSON})
echo "${offlineJSON}" > ${offlineFile}
#Readback the tx content and compare it to the current one
readback=$(cat ${offlineFile} | jq -r ".transactions[-1].txJSON")
if [[ "${txFileJSON}" == "${readback}" ]]; then
readbackReferenceScriptSize=$(cat ${offlineFile} | jq -r ".transactions[-1].referenceScriptSize")
if [[ "${txFileJSON}" == "${readback}" && "${referenceScriptSize}" == "${readbackReferenceScriptSize}" ]]; then
showOfflineFileInfo;
echo -e "\e[33mTransaction txJSON has been stored in the '$(basename ${offlineFile})'.\nYou can now transfer it to your online machine for execution.\e[0m\n";
else
Expand Down
8 changes: 6 additions & 2 deletions cardano/mainnet/01_sendAssets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -676,8 +676,10 @@ dummyReturnAmount=$(( ${totalLovelaces} - ${lovelacesToSend} ))
${cardanocli} ${cliEra} transaction build-raw ${txInString} --tx-out "${sendToAddr}+${lovelacesToSend}${assetsSendString}" --tx-out "${sendFromAddr}+${dummyReturnAmount}${assetsReturnString}" --invalid-hereafter ${ttl} --fee 200000 ${metafileParameter} --out-file ${txBodyFile}
checkError "$?"; if [ $? -ne 0 ]; then exit $?; fi

prepareReferenceScriptSpend "$txBodyFile" || exit 1

#calculate the transaction fee. new parameters since cardano-cli 8.21.0
fee=$(${cardanocli} ${cliEra} transaction calculate-min-fee --output-text --tx-body-file ${txBodyFile} --protocol-params-file <(echo ${protocolParametersJSON}) --witness-count 1 --reference-script-size 0 2> /dev/stdout)
fee=$(${cardanocli} ${cliEra} transaction calculate-min-fee --output-text --tx-body-file ${txBodyFile} --protocol-params-file <(echo ${protocolParametersJSON}) --witness-count 1 --reference-script-size "${referenceScriptSize}" 2> /dev/stdout)
if [ $? -ne 0 ]; then echo -e "\n\e[35m${fee}\e[0m\n"; exit 1; fi
fee=${fee%% *} #only get the first part of 'xxxxxx Lovelaces'

Expand Down Expand Up @@ -814,14 +816,16 @@ if [ "${ENV_SKIP_PROMPT}" == "YES" ] || ask "\n\e[33mDoes this look good for you
sendFromAddr: \"${sendFromAddr}\",
toAddr: \"${toAddr}\",
sendToAddr: \"${sendToAddr}\",
referenceScriptSize: ${referenceScriptSize},
txJSON: ${txFileJSON} } ]" <<< ${offlineJSON})
#Write the new offileFile content
offlineJSON=$( jq ".history += [ { date: \"$(date -R)\", action: \"generated and signed utxo-token-transaction from '${fromAddr}' to '${toAddr}'\" } ]" <<< ${offlineJSON})
offlineJSON=$( jq ".general += {offlineCLI: \"${versionCLI}\" }" <<< ${offlineJSON})
echo "${offlineJSON}" > ${offlineFile}
#Readback the tx content and compare it to the current one
readback=$(cat ${offlineFile} | jq -r ".transactions[-1].txJSON")
if [[ "${txFileJSON}" == "${readback}" ]]; then
readbackReferenceScriptSize=$(cat ${offlineFile} | jq -r ".transactions[-1].referenceScriptSize")
if [[ "${txFileJSON}" == "${readback}" && "${referenceScriptSize}" == "${readbackReferenceScriptSize}" ]]; then
showOfflineFileInfo;
echo -e "\e[33mTransaction txJSON has been stored in the '$(basename ${offlineFile})'.\nYou can now transfer it to your online machine for execution.\e[0m\n";
else
Expand Down
8 changes: 6 additions & 2 deletions cardano/mainnet/01_sendLovelaces.sh
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,10 @@ if [[ ${rxcnt} == 1 ]]; then #Sending ALLFUNDS or sending ALL lovelaces and no
checkError "$?"; if [ $? -ne 0 ]; then exit $?; fi
fi

prepareReferenceScriptSpend "$txBodyFile" || exit 1

#calculate the transaction fee. new parameters since cardano-cli 8.21.0
fee=$(${cardanocli} ${cliEra} transaction calculate-min-fee --output-text --tx-body-file ${txBodyFile} --protocol-params-file <(echo ${protocolParametersJSON}) --witness-count 1 --reference-script-size 0 2> /dev/stdout)
fee=$(${cardanocli} ${cliEra} transaction calculate-min-fee --output-text --tx-body-file ${txBodyFile} --protocol-params-file <(echo ${protocolParametersJSON}) --witness-count 1 --reference-script-size "${referenceScriptSize}" 2> /dev/stdout)
if [ $? -ne 0 ]; then echo -e "\n\e[35m${fee}\e[0m\n"; exit 1; fi
fee=${fee%% *} #only get the first part of 'xxxxxx Lovelaces'

Expand Down Expand Up @@ -685,14 +687,16 @@ if [ "${ENV_SKIP_PROMPT}" == "YES" ] || ask "\n\e[33mDoes this look good for you
sendFromAddr: \"${sendFromAddr}\",
toAddr: \"${toAddr}\",
sendToAddr: \"${sendToAddr}\",
referenceScriptSize: ${referenceScriptSize},
txJSON: ${txFileJSON} } ]" <<< ${offlineJSON})
#Write the new offileFile content
offlineJSON=$( jq ".history += [ { date: \"$(date -R)\", action: \"signed utxo-transaction from '${fromAddr}' to '${toAddr}'\" } ]" <<< ${offlineJSON})
offlineJSON=$( jq ".general += {offlineCLI: \"${versionCLI}\" }" <<< ${offlineJSON})
echo "${offlineJSON}" > ${offlineFile}
#Readback the tx content and compare it to the current one
readback=$(cat ${offlineFile} | jq -r ".transactions[-1].txJSON")
if [[ "${txFileJSON}" == "${readback}" ]]; then
readbackReferenceScriptSize=$(cat ${offlineFile} | jq -r ".transactions[-1].referenceScriptSize")
if [[ "${txFileJSON}" == "${readback}" && "${referenceScriptSize}" == "${readbackReferenceScriptSize}" ]]; then
showOfflineFileInfo;
echo -e "\e[33mTransaction txJSON has been stored in the '$(basename ${offlineFile})'.\nYou can now transfer it to your online machine for execution.\e[0m\n";
else
Expand Down
20 changes: 18 additions & 2 deletions cardano/mainnet/01_workOffline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ if [[ ${typeOfAddr} == ${addrTypePayment} ]]; then #Enterprise and Base UTXO ad

utxoEntryCnt=$(jq length <<< ${utxoJSON})
if [[ ${utxoEntryCnt} == 0 ]]; then echo -e "\e[35mNo funds on the Address!\e[0m\n"; exit 1; else echo -e "\e[32m${utxoEntryCnt} UTXOs\e[0m found on the Address!"; fi
referenceScriptSizes=$(queryReferenceScriptSizes "$(jq -c 'keys' <<< "${utxoJSON}")")
if [[ $? -ne 0 ]]; then exit 1; fi
echo

totalLovelaces=0; #Init for the Sum
Expand Down Expand Up @@ -429,6 +431,7 @@ if [[ ${typeOfAddr} == ${addrTypePayment} ]]; then #Enterprise and Base UTXO ad
offlineJSON=$( jq ".address.\"${checkAddr}\" += {used: \"no\" }" <<< ${offlineJSON})
offlineJSON=$( jq ".address.\"${checkAddr}\" += {type: \"${typeOfAddr}\" }" <<< ${offlineJSON})
offlineJSON=$( jq ".address.\"${checkAddr}\" += {utxoJSON: ${utxoJSON} }" <<< ${offlineJSON})
offlineJSON=$( jq ".address.\"${checkAddr}\".referenceScriptSizes = ${referenceScriptSizes}" <<< ${offlineJSON})

offlineJSON=$( jq ".history += [ { date: \"$(date -R)\", action: \"added utxo-info for '${addrName}'\" } ]" <<< ${offlineJSON})

Expand All @@ -437,8 +440,10 @@ if [[ ${typeOfAddr} == ${addrTypePayment} ]]; then #Enterprise and Base UTXO ad

#Readback the content and compare it to the current one
utxoJSON=$(jq . <<< ${utxoJSON}) # bring it into the same format
readback=$(cat ${offlineFile} | jq -r ".address.\"${checkAddr}\".utxoJSON")
if [[ "${utxoJSON}" == "${readback}" ]]; then
referenceScriptSizes=$(jq . <<< ${referenceScriptSizes})
readback=$(jq -r ".address.\"${checkAddr}\".utxoJSON" < "${offlineFile}")
readbackReferenceScriptSizes=$(jq -r ".address.\"${checkAddr}\".referenceScriptSizes" < "${offlineFile}")
if [[ "${utxoJSON}" == "${readback}" && "${referenceScriptSizes}" == "${readbackReferenceScriptSizes}" ]]; then
showOfflineFileInfo;
echo -e "\e[33mLatest Information about this address was added to the '$(basename ${offlineFile})'.\nYou can now transfer it to your offline machine to work with it, or you can\nadd another address information to the file by re-running this script.\e[0m\n";
else
Expand Down Expand Up @@ -669,8 +674,19 @@ transactionTxJSON=$(jq -r ".transactions[${transactionIdx}].txJSON" <<< ${offlin
#Write out the TxJSON to a temporary file
txFile="${tempDir}/$(basename ${transactionFromName}).tmp.txfile"
rm ${txFile} 2> /dev/null #delete an old one if present

echo "${transactionTxJSON}" > ${txFile};
checkError "$?"; if [ $? -ne 0 ]; then exit $?; fi
# Re-resolve fee-critical input metadata before any queued transaction can be submitted.
prepareReferenceScriptSpend "$txFile" || exit 1
recordedReferenceScriptSize=$(jq -c ".transactions[${transactionIdx}] | if has(\"referenceScriptSize\") then .referenceScriptSize else \"missing\" end" <<< "${offlineJSON}")
if ! jq -e --argjson resolved "${referenceScriptSize}" '
(type == "number" and . >= 0 and floor == . and . == $resolved) or
(. == "missing" and $resolved == 0)
' >/dev/null 2>&1 <<< "${recordedReferenceScriptSize}"; then
echo -e "\e[35mReference-script fee information is missing or mismatched; rebuild and re-sign this transaction with the updated scripts.\e[0m" >&2
exit 1
fi

case ${transactionType} in

Expand Down
Loading