module Singular.Registry.Deployment.Attach
(
CageParts (..)
, cageConfigFor
, verifyDeployment
, Attached (..)
, attach
) where
import Control.Monad (unless, when)
import Data.ByteString.Base16 qualified as B16
import Data.ByteString.Char8 qualified as BC
import Data.ByteString.Short qualified as SBS
import Data.List (minimumBy)
import Data.Ord (comparing)
import Data.Text (Text)
import Data.Text qualified as T
import Cardano.Crypto.Hash.Class (hashFromBytes)
import Cardano.Ledger.Api.Tx.In (TxIn (..))
import Cardano.Ledger.Api.Tx.Out (TxOut)
import Cardano.Ledger.BaseTypes (Network (..))
import Cardano.Ledger.Hashes (ScriptHash (..))
import Data.Map.Strict qualified as Map
import Singular.Registry.AssetName (deriveAssetName)
import Singular.Registry.Config (CageConfig (..))
import Singular.Registry.Deployment.Manifest
( Deployment (..)
, ReferenceScript (..)
, die
, hex
, parseOutRef
, renderOutRef
)
import Singular.Registry.Evidence qualified as Cage
import Singular.Registry.Ledger
( AssetName (..)
, Coin (..)
, ConwayEra
, TokenId (..)
)
import Singular.Registry.LedgerProvider qualified as Cage
import Singular.Registry.SessionIO qualified as Cage
import Singular.Registry.StateToken
( ReferenceRefusal (..)
, carriesReference
, findReferences
, parseRole
, renderReferenceRefusal
)
import Singular.Registry.TxBuilder.Internal.Identity
( cageAddrFromCfg
, cagePolicyIdFromCfg
, computeScriptHash
, extractCageDatum
, scriptHashBytes
, txInToRef
)
import Singular.Registry.TxBuilder.Internal.Lookup (findStateUtxo)
import Singular.Registry.Types
( CageDatum (..)
, OnChainTokenState (..)
, stateActivePolicyBytes
)
data CageParts = CageParts
{ CageParts -> ShortByteString
partsStateBytes :: SBS.ShortByteString
, CageParts -> ShortByteString
partsRequestBytes :: SBS.ShortByteString
, CageParts -> ShortByteString
partsApplicationPolicy :: SBS.ShortByteString
, CageParts -> ShortByteString
partsActivePolicy :: SBS.ShortByteString
, CageParts -> ShortByteString
partsAbsentPolicy :: SBS.ShortByteString
, CageParts -> ShortByteString
partsTerminalPolicy :: SBS.ShortByteString
, CageParts -> ShortByteString
partsConsumerScript :: SBS.ShortByteString
}
cageConfigFor :: Deployment -> CageParts -> Either String CageConfig
cageConfigFor :: Deployment -> CageParts -> Either String CageConfig
cageConfigFor Deployment
dep CageParts
parts = do
seedIn <- Text -> Either String TxIn
parseOutRef (Deployment -> Text
depSeedOutRef Deployment
dep)
when
( T.pack (hex (SBS.fromShort (partsActivePolicy parts)))
/= depRepresentativePolicy dep
)
$ Left
"this release's registry-bound active policy differs from the deployment"
let stateHash = ShortByteString -> ScriptHash
computeScriptHash (CageParts -> ShortByteString
partsStateBytes CageParts
parts)
stateHex = String -> Text
T.pack (ByteString -> String
hex (ScriptHash -> ByteString
scriptHashBytes ScriptHash
stateHash))
if stateHex /= depStatePolicy dep
then
Left
( "this release compiles the state validator to 0x"
<> T.unpack stateHex
<> " but the deployment was made with 0x"
<> T.unpack (depStatePolicy dep)
<> ": the manifest belongs to another release"
)
else
Right
CageConfig
{ cageScriptBytes = partsStateBytes parts
, requestScriptBytes = partsRequestBytes parts
, cfgScriptHash = stateHash
, cageSeed = txInToRef seedIn
, defaultProcessTime = depProcessTime dep
, defaultRetractTime = depRetractTime dep
, defaultTip = Coin (depTip dep)
, cfgApplicationPolicy = partsApplicationPolicy parts
, cfgActivePolicy = partsActivePolicy parts
, cfgAbsentPolicy = partsAbsentPolicy parts
, cfgTerminalPolicy = partsTerminalPolicy parts
, cfgConsumerScript = partsConsumerScript parts
, network = Testnet
}
tokenFor :: Deployment -> Either String TokenId
tokenFor :: Deployment -> Either String TokenId
tokenFor Deployment
dep = do
seedIn <- Text -> Either String TxIn
parseOutRef (Deployment -> Text
depSeedOutRef Deployment
dep)
let name = OnChainTxOutRef -> ByteString
deriveAssetName (TxIn -> OnChainTxOutRef
txInToRef TxIn
seedIn)
nameHex = String -> Text
T.pack (ByteString -> String
hex ByteString
name)
if nameHex /= depCageToken dep
then
Left
( "the seed "
<> T.unpack (depSeedOutRef dep)
<> " determines registry token 0x"
<> T.unpack nameHex
<> " but the manifest records 0x"
<> T.unpack (depCageToken dep)
)
else Right (TokenId (AssetName (SBS.toShort name)))
verifyDeployment
:: Cage.Session Cage.NoWitness IO
-> Deployment
-> CageParts
-> IO [String]
verifyDeployment :: Session NoWitness IO -> Deployment -> CageParts -> IO [String]
verifyDeployment Session NoWitness IO
view Deployment
dep CageParts
parts = do
cfg <- (String -> IO CageConfig)
-> (CageConfig -> IO CageConfig)
-> Either String CageConfig
-> IO CageConfig
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> IO CageConfig
forall a. String -> IO a
die CageConfig -> IO CageConfig
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Deployment -> CageParts -> Either String CageConfig
cageConfigFor Deployment
dep CageParts
parts)
tok <- either die pure (tokenFor dep)
refs <- referencesByHash view dep
(stateIn, stateOut) <- resolveStateUtxo view cfg tok
stateLive <- case extractCageDatum stateOut of
Just (StateDatum OnChainTokenState
st)
| OnChainTokenState -> ByteString
stateActivePolicyBytes OnChainTokenState
st ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ShortByteString -> ByteString
SBS.fromShort (CageParts -> ShortByteString
partsActivePolicy CageParts
parts) ->
OnChainTokenState -> IO OnChainTokenState
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure OnChainTokenState
st
Maybe CageDatum
_ ->
String -> IO OnChainTokenState
forall a. String -> IO a
die
String
"the live registry state does not configure this registry-bound representative policy"
unless
( stateProcessTime stateLive == depProcessTime dep
&& stateRetractTime stateLive == depRetractTime dep
)
$ die
( "the live registry state carries process/retract windows "
<> show (stateProcessTime stateLive, stateRetractTime stateLive)
<> " but the manifest records "
<> show (depProcessTime dep, depRetractTime dep)
)
pure
( [ "release "
<> T.unpack (depRelease dep)
<> " compiles the state validator to the recorded 0x"
<> T.unpack (depStatePolicy dep)
, "seed "
<> T.unpack (depSeedOutRef dep)
<> " determines the recorded registry token 0x"
<> T.unpack (depCageToken dep)
, "compiled representative policy agrees with the manifest and live registry configuration: 0x"
<> T.unpack (depRepresentativePolicy dep)
, "registry state carries the recorded request windows: process "
<> show (depProcessTime dep)
<> " ms, retract "
<> show (depRetractTime dep)
<> " ms"
]
<> [ "reference script "
<> T.unpack (refRole r)
<> " live at "
<> T.unpack (renderOutRef carrier)
<> " carrying 0x"
<> T.unpack (refHash r)
| (r, (carrier, _)) <- refs
]
<> [ "registry state output "
<> T.unpack (renderOutRef stateIn)
<> " carries the recorded token"
]
)
referencesByHash
:: Cage.Session Cage.NoWitness IO
-> Deployment
-> IO [(ReferenceScript, (TxIn, TxOut ConwayEra))]
referencesByHash :: Session NoWitness IO
-> Deployment -> IO [(ReferenceScript, (TxIn, TxOut ConwayEra))]
referencesByHash Session NoWitness IO
view Deployment
dep = do
recorded <- (ReferenceScript
-> IO (ReferenceScript, Maybe ReferenceRole, ScriptHash))
-> [ReferenceScript]
-> IO [(ReferenceScript, Maybe ReferenceRole, ScriptHash)]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM ReferenceScript
-> IO (ReferenceScript, Maybe ReferenceRole, ScriptHash)
roleOf (Deployment -> [ReferenceScript]
depReferenceScripts Deployment
dep)
let expected = [(ReferenceRole, ScriptHash)] -> Map ReferenceRole ScriptHash
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [(ReferenceRole
role, ScriptHash
hash) | (ReferenceScript
_, Just ReferenceRole
role, ScriptHash
hash) <- [(ReferenceScript, Maybe ReferenceRole, ScriptHash)]
recorded]
found <-
findReferences view Nothing expected (Map.keysSet expected)
>>= either (die . T.unpack . renderReferenceRefusal) pure
let carrier (a
r, Just ReferenceRole
role, ScriptHash
_) = (a, (TxIn, BabbageTxOut ConwayEra))
-> IO (a, (TxIn, BabbageTxOut ConwayEra))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (a
r, Map ReferenceRole (TxIn, BabbageTxOut ConwayEra)
found Map ReferenceRole (TxIn, BabbageTxOut ConwayEra)
-> ReferenceRole -> (TxIn, BabbageTxOut ConwayEra)
forall k a. Ord k => Map k a -> k -> a
Map.! ReferenceRole
role)
carrier (a
r, Maybe ReferenceRole
Nothing, ScriptHash
hash) = do
carried <- Session NoWitness IO -> ScriptHash -> IO (TxIn, TxOut ConwayEra)
custodyCarrier Session NoWitness IO
view ScriptHash
hash
pure (r, carried)
mapM carrier recorded
where
roleOf :: ReferenceScript
-> IO (ReferenceScript, Maybe ReferenceRole, ScriptHash)
roleOf ReferenceScript
r = do
role <-
if ReferenceScript -> Text
refRole ReferenceScript
r Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
custodyRole
then Maybe ReferenceRole -> IO (Maybe ReferenceRole)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe ReferenceRole
forall a. Maybe a
Nothing
else case Text -> Maybe ReferenceRole
parseRole (ReferenceScript -> Text
refRole ReferenceScript
r) of
Just ReferenceRole
registryRole -> Maybe ReferenceRole -> IO (Maybe ReferenceRole)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ReferenceRole -> Maybe ReferenceRole
forall a. a -> Maybe a
Just ReferenceRole
registryRole)
Maybe ReferenceRole
Nothing ->
String -> IO (Maybe ReferenceRole)
forall a. String -> IO a
die
( String
"the deployment records an unknown reference role "
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack (ReferenceScript -> Text
refRole ReferenceScript
r)
)
hash <-
maybe
( die
( "the deployment's "
<> T.unpack (refRole r)
<> " hash is not a script hash"
)
)
pure
(decodeScriptHash (refHash r))
pure (r, role, hash)
custodyRole :: Text
custodyRole :: Text
custodyRole = Text
"custody"
custodyCarrier
:: Cage.Session Cage.NoWitness IO
-> ScriptHash
-> IO (TxIn, TxOut ConwayEra)
custodyCarrier :: Session NoWitness IO -> ScriptHash -> IO (TxIn, TxOut ConwayEra)
custodyCarrier Session NoWitness IO
view ScriptHash
hash = do
answer <-
Session NoWitness IO
-> OutputQuery
-> IO (Either ReadFailure (Evidenced NoWitness Outputs))
forall w (m :: * -> *).
Session w m
-> OutputQuery -> m (Either ReadFailure (Evidenced w Outputs))
Cage.outputs Session NoWitness IO
view (ScriptHash -> OutputQuery
Cage.CarryingReferenceScript ScriptHash
hash)
IO
(Either
ReadFailure (Evidenced NoWitness [(TxIn, BabbageTxOut ConwayEra)]))
-> (Either
ReadFailure (Evidenced NoWitness [(TxIn, BabbageTxOut ConwayEra)])
-> IO [(TxIn, BabbageTxOut ConwayEra)])
-> IO [(TxIn, BabbageTxOut ConwayEra)]
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (ReadFailure -> IO [(TxIn, BabbageTxOut ConwayEra)])
-> (Evidenced NoWitness [(TxIn, BabbageTxOut ConwayEra)]
-> IO [(TxIn, BabbageTxOut ConwayEra)])
-> Either
ReadFailure (Evidenced NoWitness [(TxIn, BabbageTxOut ConwayEra)])
-> IO [(TxIn, BabbageTxOut ConwayEra)]
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either
(String -> IO [(TxIn, BabbageTxOut ConwayEra)]
forall a. String -> IO a
die (String -> IO [(TxIn, BabbageTxOut ConwayEra)])
-> (ReadFailure -> String)
-> ReadFailure
-> IO [(TxIn, BabbageTxOut ConwayEra)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> String) -> (ReadFailure -> Text) -> ReadFailure -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReferenceRefusal -> Text
renderReferenceRefusal (ReferenceRefusal -> Text)
-> (ReadFailure -> ReferenceRefusal) -> ReadFailure -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ReadFailure -> ReferenceRefusal
ReferenceUnreadable)
([(TxIn, BabbageTxOut ConwayEra)]
-> IO [(TxIn, BabbageTxOut ConwayEra)]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([(TxIn, BabbageTxOut ConwayEra)]
-> IO [(TxIn, BabbageTxOut ConwayEra)])
-> (Evidenced NoWitness [(TxIn, BabbageTxOut ConwayEra)]
-> [(TxIn, BabbageTxOut ConwayEra)])
-> Evidenced NoWitness [(TxIn, BabbageTxOut ConwayEra)]
-> IO [(TxIn, BabbageTxOut ConwayEra)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Evidenced NoWitness [(TxIn, BabbageTxOut ConwayEra)]
-> [(TxIn, BabbageTxOut ConwayEra)]
forall w a. Evidenced w a -> a
Cage.value)
case filter (carriesReference hash . snd) answer of
[] ->
String -> IO (TxIn, BabbageTxOut ConwayEra)
forall a. String -> IO a
die
( String
"the deployment's custody script 0x"
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> ByteString -> String
hex (ScriptHash -> ByteString
scriptHashBytes ScriptHash
hash)
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" has no live carrier this provider reports"
)
[(TxIn, BabbageTxOut ConwayEra)]
carriers -> (TxIn, BabbageTxOut ConwayEra) -> IO (TxIn, BabbageTxOut ConwayEra)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (((TxIn, BabbageTxOut ConwayEra)
-> (TxIn, BabbageTxOut ConwayEra) -> Ordering)
-> [(TxIn, BabbageTxOut ConwayEra)]
-> (TxIn, BabbageTxOut ConwayEra)
forall (t :: * -> *) a.
Foldable t =>
(a -> a -> Ordering) -> t a -> a
minimumBy (((TxIn, BabbageTxOut ConwayEra) -> TxIn)
-> (TxIn, BabbageTxOut ConwayEra)
-> (TxIn, BabbageTxOut ConwayEra)
-> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing (TxIn, BabbageTxOut ConwayEra) -> TxIn
forall a b. (a, b) -> a
fst) [(TxIn, BabbageTxOut ConwayEra)]
carriers)
resolveStateUtxo
:: Cage.Session Cage.NoWitness IO
-> CageConfig
-> TokenId
-> IO (TxIn, TxOut ConwayEra)
resolveStateUtxo :: Session NoWitness IO
-> CageConfig -> TokenId -> IO (TxIn, TxOut ConwayEra)
resolveStateUtxo Session NoWitness IO
view CageConfig
cfg TokenId
tok = do
let stateAddr :: Addr
stateAddr = CageConfig -> Network -> Addr
cageAddrFromCfg CageConfig
cfg Network
Testnet
utxos <- Session NoWitness IO -> Addr -> IO Outputs
forall w. Session w IO -> Addr -> IO Outputs
Cage.outputsAt Session NoWitness IO
view Addr
stateAddr
case findStateUtxo (cagePolicyIdFromCfg cfg) tok utxos of
Just (TxIn, TxOut ConwayEra)
u -> (TxIn, BabbageTxOut ConwayEra) -> IO (TxIn, BabbageTxOut ConwayEra)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (TxIn, TxOut ConwayEra)
(TxIn, BabbageTxOut ConwayEra)
u
Maybe (TxIn, TxOut ConwayEra)
Nothing ->
String -> IO (TxIn, BabbageTxOut ConwayEra)
forall a. String -> IO a
die
String
"no output at the registry address carries the recorded \
\token; the node does not know this deployment (wrong \
\network, or the registry was never booted here)"
data Attached = Attached
{ Attached -> CageConfig
attCfg :: CageConfig
, Attached -> TokenId
attToken :: TokenId
, Attached -> Outputs
attRefUtxos :: [(TxIn, TxOut ConwayEra)]
, Attached -> (TxIn, TxOut ConwayEra)
attStateUtxo :: (TxIn, TxOut ConwayEra)
}
attach
:: Cage.Session Cage.NoWitness IO
-> Deployment
-> CageParts
-> IO Attached
attach :: Session NoWitness IO -> Deployment -> CageParts -> IO Attached
attach Session NoWitness IO
view Deployment
dep CageParts
parts = do
cfg <- (String -> IO CageConfig)
-> (CageConfig -> IO CageConfig)
-> Either String CageConfig
-> IO CageConfig
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> IO CageConfig
forall a. String -> IO a
die CageConfig -> IO CageConfig
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Deployment -> CageParts -> Either String CageConfig
cageConfigFor Deployment
dep CageParts
parts)
tok <- either die pure (tokenFor dep)
refs <- referencesByHash view dep
state <- resolveStateUtxo view cfg tok
pure
Attached
{ attCfg = cfg
, attToken = tok
, attRefUtxos = map snd refs
, attStateUtxo = state
}
decodeScriptHash :: Text -> Maybe ScriptHash
decodeScriptHash :: Text -> Maybe ScriptHash
decodeScriptHash Text
t = case ByteString -> Either String ByteString
B16.decode (String -> ByteString
BC.pack (Text -> String
T.unpack Text
t)) of
Right ByteString
raw -> Hash ADDRHASH EraIndependentScript -> ScriptHash
ScriptHash (Hash ADDRHASH EraIndependentScript -> ScriptHash)
-> Maybe (Hash ADDRHASH EraIndependentScript) -> Maybe ScriptHash
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ByteString -> Maybe (Hash ADDRHASH EraIndependentScript)
forall h a. HashAlgorithm h => ByteString -> Maybe (Hash h a)
hashFromBytes ByteString
raw
Left String
_ -> Maybe ScriptHash
forall a. Maybe a
Nothing