Skip to content
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
6 changes: 6 additions & 0 deletions .changes/20260720_cardano_rpc_txoutput_raw_bytes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project: cardano-rpc
pr: 1258
kind:
- bugfix
description: |
Fix wire encoding of two TxOutput fields: address now carries raw ledger address bytes instead of bech32/base58 text, and Datum.hash now carries the 32-byte datum hash instead of the datum CBOR for inline datums, matching other UTxO RPC implementations.
1 change: 1 addition & 0 deletions cardano-rpc/cardano-rpc.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ library
build-depends:
aeson,
base,
base16-bytestring,
bytestring,
cardano-api >=11.2,
cardano-binary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ module Cardano.Rpc.Server.Internal.UtxoRpc.Type.TxOutput
)
where

import Cardano.Api.Address
import Cardano.Api.Era
import Cardano.Api.Error
import Cardano.Api.Experimental.Era
Expand All @@ -36,9 +35,9 @@ import Cardano.Binary qualified as CBOR

import RIO hiding (toList)

import Data.ByteString.Base16 qualified as Base16
import Data.ByteString.Char8 qualified as BSC
import Data.ProtoLens (defMessage)
import Data.Text qualified as T
import Data.Text.Encoding qualified as T
import GHC.IsList
import Network.GRPC.Spec

Expand Down Expand Up @@ -123,12 +122,12 @@ txOutToUtxoRpcTxOutput sbe (TxOut addressInEra txOutValue datum script) = do
TxOutDatumInline _ hashableScriptData ->
Just $
defMessage
& U5c.hash .~ serialiseToCBOR hashableScriptData
& U5c.hash .~ serialiseToRawBytes (hashScriptDataBytes hashableScriptData)
& U5c.payload .~ scriptDataToUtxoRpcPlutusData (getScriptData hashableScriptData)
& U5c.originalCbor .~ getOriginalScriptDataBytes hashableScriptData

defMessage
& U5c.address .~ T.encodeUtf8 (shelleyBasedEraConstraints sbe $ serialiseAddress addressInEra)
& U5c.address .~ shelleyBasedEraConstraints sbe (serialiseToRawBytes addressInEra)
& U5c.coin .~ inject (L.unCoin (txOutValueToLovelace txOutValue))
& U5c.assets .~ multiAsset
& U5c.maybe'datum .~ datumRpc
Expand All @@ -143,11 +142,15 @@ utxoRpcTxOutputToTxOut
-> m (TxOut CtxUTxO era)
utxoRpcTxOutputToTxOut txOutput = do
let era = useEra @era
addrUtf8 <- liftEitherError $ T.decodeUtf8' (txOutput ^. U5c.address)
let addressBytes = txOutput ^. U5c.address
annotateError (SerialiseAsRawBytesError msg) =
SerialiseAsRawBytesError $
msg <> ", address (hex): " <> BSC.unpack (Base16.encode addressBytes)
address <-
maybe (throwM . stringException $ "Cannot decode address: " <> T.unpack addrUtf8) pure $
obtainCommonConstraints era $
deserialiseAddress asType addrUtf8
obtainCommonConstraints era $
liftEitherError $
first annotateError $
deserialiseFromRawBytes asType addressBytes
datum <-
case txOutput ^. U5c.maybe'datum of
Just datumRpc ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ module Test.Cardano.Rpc.FetchBlockTx where

import Cardano.Api (SlotNo (..))
import Cardano.Api.Address
( serialiseAddress
, toShelleyAddr
( toShelleyAddr
, toShelleyStakeAddr
, toShelleyStakeCredential
)
Expand All @@ -32,7 +31,6 @@ import RIO hiding (toList)

import Data.Map.Strict qualified as M
import Data.ProtoLens (decodeMessage, encodeMessage)
import Data.Text.Encoding qualified as T
import GHC.IsList (fromList, toList)
import GHC.Stack (withFrozenCallStack)
import Network.GRPC.Spec (Proto (..))
Expand Down Expand Up @@ -97,7 +95,7 @@ txToUtxoRpcTxProjections sbe = H.withTests 40 . H.property $ anyEraTxConstraints
expectedAddress ledgerOutput =
case fromShelleyTxOut sbe ledgerOutput of
TxOut addressInEra _ _ _ ->
shelleyBasedEraConstraints sbe $ T.encodeUtf8 (serialiseAddress addressInEra)
shelleyBasedEraConstraints sbe $ serialiseToRawBytes addressInEra
map (\o -> (o ^. U5c.address, o ^. U5c.coin)) protoOutputs
=== map
(\o -> (expectedAddress o, inject $ o ^. L.coinTxOutL))
Expand Down Expand Up @@ -331,7 +329,7 @@ hprop_tx_to_utxorpc_tx_injected_optional_fields = H.withTests 10 . H.property $
totalCollateral === inject (L.Coin totalCollateralCoin)
collateralReturn <- H.nothingFail $ collateral ^. U5c.maybe'collateralReturn
collateralReturn ^. U5c.address
=== shelleyBasedEraConstraints sbe (T.encodeUtf8 (serialiseAddress returnAddress))
=== shelleyBasedEraConstraints sbe (serialiseToRawBytes returnAddress)
collateralReturn ^. U5c.coin === inject (L.Coin returnCoin)

H.note_ "The proposal carries the injected deposit, return account, anchor and action"
Expand Down
31 changes: 29 additions & 2 deletions cardano-rpc/test/cardano-rpc-test/Test/Cardano/Rpc/TxOutput.hs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Test.Cardano.Rpc.TxOutput where

import Cardano.Api.Experimental.Era
import Cardano.Api.Plutus (hashScriptDataBytes)
import Cardano.Api.Serialise.Raw
import Cardano.Api.Tx
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as U5c
import Cardano.Rpc.Server.Internal.UtxoRpc.Type

import RIO
Expand All @@ -15,15 +20,37 @@ import Test.Gen.Cardano.Api.Typed

import Hedgehog
import Hedgehog qualified as H
import Hedgehog.Extras qualified as H

era :: Era ConwayEra
era = ConwayEra

-- | Test if TxOut in UTXO context does roundtrip
hprop_roundtrip_tx_output :: Property
hprop_roundtrip_tx_output = H.property $ do
let era = ConwayEra

txOut <- forAll $ genTxOutUTxOContext (convert era)

H.tripping
txOut
(txOutToUtxoRpcTxOutput (convert era))
(first @Either displayException . utxoRpcTxOutputToTxOut)

-- | Test that TxOutput fields carry raw bytes on the wire
hprop_tx_output_wire_format :: Property
hprop_tx_output_wire_format = H.property $ do
txOut@(TxOut addressInEra _ datum _) <- forAll $ genTxOutUTxOContext (convert era)

let protoTxOutput = txOutToUtxoRpcTxOutput (convert era) txOut

H.note_ "Address field carries raw ledger address bytes"
protoTxOutput ^. U5c.address === serialiseToRawBytes addressInEra

case datum of
TxOutDatumNone -> pure ()
TxOutDatumHash _ scriptDataHash -> do
H.note_ "Datum hash field carries the raw script data hash"
protoTxOutput ^. U5c.datum . U5c.hash === serialiseToRawBytes scriptDataHash
TxOutDatumInline _ hashableScriptData -> do
H.note_ "Inline datum hash field carries the datum hash, not the datum CBOR"
protoTxOutput ^. U5c.datum . U5c.hash
=== serialiseToRawBytes (hashScriptDataBytes hashableScriptData)
Loading