From c8dc4909c41c28d1914d11ea3d564a868232b248 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 2 May 2024 18:21:31 +0300 Subject: [PATCH 01/22] Add reading and writing to file --- src/Concordium/Client/Commands.hs | 28 ++++++++-- src/Concordium/Client/Output.hs | 64 +++++++++++----------- src/Concordium/Client/Runner.hs | 90 ++++++++++++++++++++++++------- 3 files changed, 126 insertions(+), 56 deletions(-) diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 9a6b3391..772dec79 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -181,7 +181,11 @@ registerDataParser = <|> (RegisterRaw <$> strOption (long "raw" <> metavar "FILE" <> help "File with raw bytes to be registered on chain.")) data TransactionCmd - = TransactionSubmit + = TransactionSignAndSubmit + { tsFile :: !FilePath, + tsInteractionOpts :: !InteractionOpts + } + | TransactionSubmit { tsFile :: !FilePath, tsInteractionOpts :: !InteractionOpts } @@ -432,6 +436,7 @@ data TransactionOpts energyOrMaybe = TransactionOpts data InteractionOpts = InteractionOpts { ioConfirm :: !Bool, + ioSubmit :: !Bool, ioTail :: !Bool } deriving (Show) @@ -655,8 +660,8 @@ interactionOptsParser :: Parser InteractionOpts interactionOptsParser = InteractionOpts <$> (not <$> switch (long "no-confirm" <> help "Do not ask for confirmation before proceeding to send the transaction.")) + <*> (not <$> switch (long "no-submit" <> help "Do not submit transaction on-chain. Write signed transaction to file instead.")) <*> (not <$> switch (long "no-wait" <> help "Exit right after sending the transaction without waiting for it to be committed and finalized.")) - programOptions :: Parser Options programOptions = Options @@ -700,7 +705,8 @@ transactionCmds = ( info ( TransactionCmd <$> hsubparser - ( transactionSubmitCmd + ( transactionSignAndSubmitCmd + <> transactionSubmitCmd <> transactionStatusCmd <> transactionSendCcdCmd <> transactionWithScheduleCmd @@ -712,16 +718,28 @@ transactionCmds = (progDesc "Commands for submitting and inspecting transactions.") ) +transactionSignAndSubmitCmd :: Mod CommandFields TransactionCmd +transactionSignAndSubmitCmd = + command + "sign-and-submit" + ( info + ( TransactionSignAndSubmit + <$> strArgument (metavar "FILE" <> help "File containing the transaction parameters in JSON format.") + <*> interactionOptsParser + ) + (progDesc "Create transaction, sign it, and send it to the node.") + ) + transactionSubmitCmd :: Mod CommandFields TransactionCmd transactionSubmitCmd = command "submit" ( info ( TransactionSubmit - <$> strArgument (metavar "FILE" <> help "File containing the transaction parameters in JSON format.") + <$> strArgument (metavar "FILE" <> help "File containing a signed transaction in JSON format.") <*> interactionOptsParser ) - (progDesc "Parse transaction and send it to the node.") + (progDesc "Parse signed transaction and send it to the node.") ) transactionDeployCredentialCmd :: Mod CommandFields TransactionCmd diff --git a/src/Concordium/Client/Output.hs b/src/Concordium/Client/Output.hs index f6e918b1..5efe6075 100644 --- a/src/Concordium/Client/Output.hs +++ b/src/Concordium/Client/Output.hs @@ -47,7 +47,7 @@ import Data.Bool import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Short as BSS -import Data.Either (isLeft) +import Data.Either (isRight) import Data.Functor import Data.List (foldl', intercalate, nub, partition, sortOn) import qualified Data.Map.Strict as Map @@ -782,68 +782,68 @@ showEvent :: Maybe String showEvent verbose ciM = \case Types.ModuleDeployed ref -> - verboseOrNothing $ printf "module '%s' deployed" (show ref) + verboseOrNothing $ printf "module '%s' deployed." (show ref) Types.ContractInitialized{..} -> verboseOrNothing $ [i|initialized contract '#{ecAddress}' using init function '#{ecInitName}' from module '#{ecRef}' |] - <> [i|with #{showCcd ecAmount}\n#{showLoggedEvents ecEvents}|] + <> [i|with #{showCcd ecAmount}.\n#{showLoggedEvents ecEvents}|] Types.Updated{..} -> verboseOrNothing $ [i|sent message to function '#{euReceiveName}' with #{showParameter euReceiveName euMessage} and #{showCcd euAmount} |] - <> [i|from #{showAddress euInstigator} to #{showAddress $ Types.AddressContract euAddress}\n|] + <> [i|from #{showAddress euInstigator} to #{showAddress $ Types.AddressContract euAddress}.\n|] <> [i|#{showLoggedEvents euEvents}|] Types.Transferred{..} -> - verboseOrNothing $ printf "transferred %s from %s to %s" (showCcd etAmount) (showAddress etFrom) (showAddress etTo) + verboseOrNothing $ printf "transferred %s from %s to %s." (showCcd etAmount) (showAddress etFrom) (showAddress etTo) Types.AccountCreated addr -> - verboseOrNothing $ printf "account '%s' created" (show addr) + verboseOrNothing $ printf "account '%s' created." (show addr) Types.CredentialDeployed{..} -> - verboseOrNothing $ printf "credential with registration '%s' deployed onto account '%s'" (show ecdRegId) (show ecdAccount) + verboseOrNothing $ printf "credential with registration '%s' deployed onto account '%s'." (show ecdRegId) (show ecdAccount) Types.BakerAdded{..} -> let restakeString :: String = if ebaRestakeEarnings then "Earnings are added to the stake." else "Earnings are not added to the stake." in verboseOrNothing $ printf "validator %s added, staking %s CCD. %s" (showBaker ebaBakerId ebaAccount) (Types.amountToString ebaStake) restakeString Types.BakerRemoved{..} -> - verboseOrNothing $ printf "validator %s, removed" (showBaker ebrBakerId ebrAccount) + verboseOrNothing $ printf "validator %s, removed." (showBaker ebrBakerId ebrAccount) Types.BakerStakeIncreased{..} -> - verboseOrNothing $ printf "validator %s stake increased to %s" (showBaker ebsiBakerId ebsiAccount) (showCcd ebsiNewStake) + verboseOrNothing $ printf "validator %s stake increased to %s." (showBaker ebsiBakerId ebsiAccount) (showCcd ebsiNewStake) Types.BakerStakeDecreased{..} -> - verboseOrNothing $ printf "validator %s stake decreased to %s" (showBaker ebsiBakerId ebsiAccount) (showCcd ebsiNewStake) + verboseOrNothing $ printf "validator %s stake decreased to %s." (showBaker ebsiBakerId ebsiAccount) (showCcd ebsiNewStake) Types.BakerSetRestakeEarnings{..} -> - verboseOrNothing $ printf "validator %s restake earnings %s" (showBaker ebsreBakerId ebsreAccount) (if ebsreRestakeEarnings then "set" :: String else "unset") + verboseOrNothing $ printf "validator %s restake earnings %s." (showBaker ebsreBakerId ebsreAccount) (if ebsreRestakeEarnings then "set" :: String else "unset") Types.BakerKeysUpdated{..} -> - verboseOrNothing $ printf "validator %s keys updated" (showBaker ebkuBakerId ebkuAccount) + verboseOrNothing $ printf "validator %s keys updated." (showBaker ebkuBakerId ebkuAccount) Types.CredentialsUpdated{..} -> verboseOrNothing $ [i|credentials on account #{cuAccount} have been updated.\nCredentials #{cuRemovedCredIds} have been removed, and credentials #{cuNewCredIds} have been added.\nThe new account threshold is #{cuNewThreshold}.|] Types.BakerSetOpenStatus{..} -> - verboseOrNothing $ printf "validator %s open status changed to %s" (showBaker ebsosBakerId ebsosAccount) (show ebsosOpenStatus) + verboseOrNothing $ printf "validator %s open status changed to %s." (showBaker ebsosBakerId ebsosAccount) (show ebsosOpenStatus) Types.BakerSetMetadataURL{..} -> - verboseOrNothing $ printf "validator %s URL changed to %s" (showBaker ebsmuBakerId ebsmuAccount) (show ebsmuMetadataURL) + verboseOrNothing $ printf "validator %s URL changed to %s." (showBaker ebsmuBakerId ebsmuAccount) (show ebsmuMetadataURL) Types.BakerSetTransactionFeeCommission{..} -> - verboseOrNothing $ printf "validator %s changed transaction fee commission to %s" (showBaker ebstfcBakerId ebstfcAccount) (show ebstfcTransactionFeeCommission) + verboseOrNothing $ printf "validator %s changed transaction fee commission to %s." (showBaker ebstfcBakerId ebstfcAccount) (show ebstfcTransactionFeeCommission) Types.BakerSetBakingRewardCommission{..} -> - verboseOrNothing $ printf "validator %s changed block reward commission to %s" (showBaker ebsbrcBakerId ebsbrcAccount) (show ebsbrcBakingRewardCommission) + verboseOrNothing $ printf "validator %s changed block reward commission to %s." (showBaker ebsbrcBakerId ebsbrcAccount) (show ebsbrcBakingRewardCommission) Types.BakerSetFinalizationRewardCommission{..} -> - verboseOrNothing $ printf "validator %s changed finalization reward commission to %s" (showBaker ebsfrcBakerId ebsfrcAccount) (show ebsfrcFinalizationRewardCommission) + verboseOrNothing $ printf "validator %s changed finalization reward commission to %s." (showBaker ebsfrcBakerId ebsfrcAccount) (show ebsfrcFinalizationRewardCommission) Types.DelegationStakeIncreased{..} -> - verboseOrNothing $ printf "delegator %s stake increased to %s" (showDelegator edsiDelegatorId edsiAccount) (showCcd edsiNewStake) + verboseOrNothing $ printf "delegator %s stake increased to %s." (showDelegator edsiDelegatorId edsiAccount) (showCcd edsiNewStake) Types.DelegationStakeDecreased{..} -> - verboseOrNothing $ printf "delegator %s stake decreased to %s" (showDelegator edsdDelegatorId edsdAccount) (showCcd edsdNewStake) + verboseOrNothing $ printf "delegator %s stake decreased to %s." (showDelegator edsdDelegatorId edsdAccount) (showCcd edsdNewStake) Types.DelegationSetRestakeEarnings{..} -> - verboseOrNothing $ printf "delegator %s restake earnings changed to %s" (showDelegator edsreDelegatorId edsreAccount) (show edsreRestakeEarnings) + verboseOrNothing $ printf "delegator %s restake earnings changed to %s." (showDelegator edsreDelegatorId edsreAccount) (show edsreRestakeEarnings) Types.DelegationSetDelegationTarget{..} -> - verboseOrNothing $ printf "delegator %s delegation target changed to %s" (showDelegator edsdtDelegatorId edsdtAccount) (showDelegationTarget edsdtDelegationTarget) + verboseOrNothing $ printf "delegator %s delegation target changed to %s." (showDelegator edsdtDelegatorId edsdtAccount) (showDelegationTarget edsdtDelegationTarget) Types.DelegationAdded{..} -> - verboseOrNothing $ printf "delegator %s added" (showDelegator edaDelegatorId edaAccount) + verboseOrNothing $ printf "delegator %s added." (showDelegator edaDelegatorId edaAccount) Types.DelegationRemoved{..} -> - verboseOrNothing $ printf "delegator %s removed" (showDelegator edrDelegatorId edrAccount) - Types.CredentialKeysUpdated cid -> verboseOrNothing $ printf "credential keys updated for credential with credId %s" (show cid) - Types.NewEncryptedAmount{..} -> verboseOrNothing $ printf "shielded amount received on account '%s' with index '%s'" (show neaAccount) (show neaNewIndex) - Types.EncryptedAmountsRemoved{..} -> verboseOrNothing $ printf "shielded amounts removed on account '%s' up to index '%s' with a resulting self shielded amount of '%s'" (show earAccount) (show earUpToIndex) (show earNewAmount) - Types.AmountAddedByDecryption{..} -> verboseOrNothing $ printf "transferred %s from the shielded balance to the public balance on account '%s'" (showCcd aabdAmount) (show aabdAccount) - Types.EncryptedSelfAmountAdded{..} -> verboseOrNothing $ printf "transferred %s from the public balance to the shielded balance on account '%s' with a resulting self shielded balance of '%s'" (showCcd eaaAmount) (show eaaAccount) (show eaaNewAmount) + verboseOrNothing $ printf "delegator %s removed." (showDelegator edrDelegatorId edrAccount) + Types.CredentialKeysUpdated cid -> verboseOrNothing $ printf "credential keys updated for credential with credId %s." (show cid) + Types.NewEncryptedAmount{..} -> verboseOrNothing $ printf "shielded amount received on account '%s' with index '%s'." (show neaAccount) (show neaNewIndex) + Types.EncryptedAmountsRemoved{..} -> verboseOrNothing $ printf "shielded amounts removed on account '%s' up to index '%s' with a resulting self shielded amount of '%s'." (show earAccount) (show earUpToIndex) (show earNewAmount) + Types.AmountAddedByDecryption{..} -> verboseOrNothing $ printf "transferred %s from the shielded balance to the public balance on account '%s'." (showCcd aabdAmount) (show aabdAccount) + Types.EncryptedSelfAmountAdded{..} -> verboseOrNothing $ printf "transferred %s from the public balance to the shielded balance on account '%s' with a resulting self shielded balance of '%s'." (showCcd eaaAmount) (show eaaAccount) (show eaaNewAmount) Types.UpdateEnqueued{..} -> verboseOrNothing $ printf "Enqueued chain update, effective at %s:\n%s" (showTimeFormatted (timeFromTransactionExpiryTime ueEffectiveTime)) (show uePayload) Types.TransferredWithSchedule{..} -> - verboseOrNothing $ printf "Sent transfer with schedule %s" (intercalate ", " . map (\(a, b) -> showTimeFormatted (Time.timestampToUTCTime a) ++ ": " ++ showCcd b) $ etwsAmount) + verboseOrNothing $ printf "Sent transfer with schedule %s." (intercalate ", " . map (\(a, b) -> showTimeFormatted (Time.timestampToUTCTime a) ++ ": " ++ showCcd b) $ etwsAmount) Types.DataRegistered{} -> verboseOrNothing [i|Registered data on chain.|] Types.TransferMemo{..} -> @@ -904,10 +904,10 @@ showEvent verbose ciM = \case showLoggedEvents :: [Wasm.ContractEvent] -> String showLoggedEvents [] = "No contract events were emitted." showLoggedEvents evs = - [i|#{length evs} contract events were emitted|] + [i|#{length evs} contract #{if length evs > 1 then "events were" else ("event was" :: String)} emitted|] <> ( if isNothing eventSchemaM then [i| but no event schema was provided nor found in the contract module. |] - else [i|, of which #{length $ filter isLeft $ map showContractEvent evs} were succesfully parsed. |] + else [i|, of which #{length $ filter isRight $ map showContractEvent evs} #{if (length $ filter isRight $ map showContractEvent evs) > 1 then "were" else ("was" :: String)} successfully parsed. |] ) <> [i|Got:\n|] <> intercalate "\n" (map fromEither (map showContractEvent evs)) @@ -920,7 +920,7 @@ showEvent verbose ciM = \case eventSchemaM = getEventSchema =<< ciM -- Show a string representation of the contract event. -- Returns @Right@ containing a JSON representation of the event if a schema was present - -- and the event could be succesfully parsed using it. + -- and the event could be successfully parsed using it. -- Returns @Left@ containing a hexadecimal representation of the raw event data otherwise. showContractEvent :: Wasm.ContractEvent -> Either String String showContractEvent ce@(Wasm.ContractEvent bs) = case toJSONString eventSchemaM bs of diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index c58e8246..b4e1cd46 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -653,7 +653,7 @@ getTxContractInfoWithSchemas schemaFile status = do processTransactionCmd :: TransactionCmd -> Maybe FilePath -> Verbose -> Backend -> IO () processTransactionCmd action baseCfgDir verbose backend = case action of - TransactionSubmit fname intOpts -> do + TransactionSignAndSubmit fname intOpts -> do -- TODO Ensure that the "nonce" field is optional in the payload. source <- handleReadFile BSL.readFile fname @@ -665,7 +665,38 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - -- logSuccess [ "transaction successfully completed" ] + logSuccess [ "transaction successfully completed" ] + + TransactionSubmit fname intOpts -> do + jsonFileContent <- liftIO $ BSL8.readFile fname + + -- TODO: Properly decode and display, especially the payload part + -- TODO: Ask for confirmation if (ioConfirm intOpts) + logInfo [[i| #{jsonFileContent}.|]] + + -- Decode JSON file content into a BareBlockItem + let bareBlockItem :: Types.BareBlockItem + bareBlockItem = case decode jsonFileContent of + Just item -> item + Nothing -> error "Failed to decode JSON file content" + + withClient backend $ do + -- Send transaction on chain + sbiRes <- sendBlockItem bareBlockItem + let res = case sbiRes of + StatusOk resp -> Right resp + StatusNotOk (status, err) -> Left [i|GRPC response with status '#{status}': #{err}|] + StatusInvalid -> Left "GRPC response contained an invalid status code." + RequestFailed err -> Left $ "I/O error: " <> err + + case res of + Left err -> logFatal ["Transaction not accepted by the node: " <> err] + Right _ -> do + let hash = getBlockItemHash bareBlockItem + logSuccess [printf "transaction '%s' sent to the node" (show hash)] + when (ioTail intOpts) $ do + tailTransaction_ verbose hash + logSuccess [ "transaction successfully completed" ] TransactionDeployCredential fname intOpts -> do source <- handleReadFile BSL.readFile fname @@ -675,7 +706,7 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - -- logSuccess [ "credential deployed successfully" ] + logSuccess [ "credential deployed successfully" ] TransactionStatus h schemaFile -> do hash <- case parseTransactionHash h of @@ -1462,7 +1493,7 @@ accountDecryptTransactionConfirm AccountDecryptTransactionConfig{..} confirm = d confirmed <- askConfirmation Nothing unless confirmed exitTransactionCancelled --- | Encode, sign, and send transaction off to the node. +-- | Encode, and sign transaction. -- If confirmNonce is set, the user is asked to confirm using the next nonce -- if there are pending transactions. startTransaction :: @@ -1488,17 +1519,12 @@ startTransaction txCfg pl confirmNonce maybeAccKeys = do Nothing -> liftIO $ failOnError $ decryptAccountKeyMapInteractive esdKeys Nothing Nothing let sender = applyAlias tcAlias naAddr let tx = signEncodedTransaction pl sender energy nonce expiry accountKeyMap + when (isJust tcAlias) $ logInfo [[i|Using the alias #{sender} as the sender of the transaction instead of #{naAddr}.|]] - sbiRes <- sendBlockItem tx - let res = case sbiRes of - StatusOk resp -> Right resp - StatusNotOk (status, err) -> Left [i|GRPC response with status '#{status}': #{err}|] - StatusInvalid -> Left "GRPC response contained an invalid status code." - RequestFailed err -> Left $ "I/O error: " <> err - case res of - Left err -> logFatal ["Transaction not accepted by the node: " <> err] - Right _ -> return tx + + return tx + -- | Fetch next nonces relative to the account's most recently committed and -- pending transactions, respectively. @@ -1537,7 +1563,8 @@ sendAndTailTransaction_ :: ClientMonad m () sendAndTailTransaction_ verbose txCfg pl intOpts = void $ sendAndTailTransaction verbose txCfg pl intOpts --- | Send a transaction and optionally tail it (see 'tailTransaction' below). +-- | Sign a transaction and either send it to the node or write it to a file. +-- If send to the node, optionally tail it (see 'tailTransaction' below). -- If tailed, it returns the TransactionStatusResult of the finalized status, -- otherwise the return value is @Nothing@. sendAndTailTransaction :: @@ -1553,11 +1580,36 @@ sendAndTailTransaction :: ClientMonad m (Maybe TransactionStatusResult) sendAndTailTransaction verbose txCfg pl intOpts = do tx <- startTransaction txCfg pl (ioConfirm intOpts) Nothing - let hash = getBlockItemHash tx - logSuccess [printf "transaction '%s' sent to the node" (show hash)] - if ioTail intOpts - then Just <$> tailTransaction verbose hash - else return Nothing + + if (ioSubmit intOpts) + then do + logInfo [[i| Send signed transaction to node.|]] + + -- Send transaction on chain + sbiRes <- sendBlockItem tx + let res = case sbiRes of + StatusOk resp -> Right resp + StatusNotOk (status, err) -> Left [i|GRPC response with status '#{status}': #{err}|] + StatusInvalid -> Left "GRPC response contained an invalid status code." + RequestFailed err -> Left $ "I/O error: " <> err + case res of + Left err -> logFatal ["Transaction not accepted by the node: " <> err] + Right _ -> do + let hash = getBlockItemHash tx + logSuccess [printf "transaction '%s' sent to the node" (show hash)] + + if ioTail intOpts + then Just <$> tailTransaction verbose hash + else return Nothing + else do + logInfo [[i| Write signed transaction to file. Don't send it to the node.|]] + -- FIXME: pass in output file via flag. + let outFile = "./transaction.json" + + let txJson = AE.encode tx + success <- liftIO $ handleWriteFile BSL.writeFile PromptBeforeOverwrite verbose outFile txJson + when success $ logSuccess [[i|Wrote transaction successfully to the file '#{outFile}'|]] + return Nothing -- | Continuously query and display transaction status until the transaction is finalized. tailTransaction_ :: (MonadIO m) => Bool -> Types.TransactionHash -> ClientMonad m () From 044b4ad7d43e1e2b606a0601c02d488735a0d27f Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 2 May 2024 18:44:34 +0300 Subject: [PATCH 02/22] Update submodule link --- deps/concordium-base | 2 +- src/Concordium/Client/Commands.hs | 2 +- src/Concordium/Client/Runner.hs | 12 ++++-------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 9b409fe3..7308605c 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 9b409fe38b9d4a15675f3b20e3d965b10f31db50 +Subproject commit 7308605c14db5f5e66b426f8596d699e6bfae822 diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 772dec79..57995b10 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -706,7 +706,7 @@ transactionCmds = ( TransactionCmd <$> hsubparser ( transactionSignAndSubmitCmd - <> transactionSubmitCmd + <> transactionSubmitCmd <> transactionStatusCmd <> transactionSendCcdCmd <> transactionWithScheduleCmd diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index b4e1cd46..2ee6ee56 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -665,8 +665,7 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - logSuccess [ "transaction successfully completed" ] - + logSuccess ["transaction successfully completed"] TransactionSubmit fname intOpts -> do jsonFileContent <- liftIO $ BSL8.readFile fname @@ -688,7 +687,7 @@ processTransactionCmd action baseCfgDir verbose backend = StatusNotOk (status, err) -> Left [i|GRPC response with status '#{status}': #{err}|] StatusInvalid -> Left "GRPC response contained an invalid status code." RequestFailed err -> Left $ "I/O error: " <> err - + case res of Left err -> logFatal ["Transaction not accepted by the node: " <> err] Right _ -> do @@ -696,8 +695,7 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - logSuccess [ "transaction successfully completed" ] - + logSuccess ["transaction successfully completed"] TransactionDeployCredential fname intOpts -> do source <- handleReadFile BSL.readFile fname withClient backend $ do @@ -706,8 +704,7 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - logSuccess [ "credential deployed successfully" ] - + logSuccess ["credential deployed successfully"] TransactionStatus h schemaFile -> do hash <- case parseTransactionHash h of Nothing -> logFatal [printf "invalid transaction hash '%s'" h] @@ -1525,7 +1522,6 @@ startTransaction txCfg pl confirmNonce maybeAccKeys = do return tx - -- | Fetch next nonces relative to the account's most recently committed and -- pending transactions, respectively. -- If they match, the nonce is returned. From 95e0c96ab4b3d221718472132027573c6f657f74 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 2 May 2024 18:52:17 +0300 Subject: [PATCH 03/22] Remove changes from previous PR --- src/Concordium/Client/Output.hs | 64 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Concordium/Client/Output.hs b/src/Concordium/Client/Output.hs index 5efe6075..f6e918b1 100644 --- a/src/Concordium/Client/Output.hs +++ b/src/Concordium/Client/Output.hs @@ -47,7 +47,7 @@ import Data.Bool import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Short as BSS -import Data.Either (isRight) +import Data.Either (isLeft) import Data.Functor import Data.List (foldl', intercalate, nub, partition, sortOn) import qualified Data.Map.Strict as Map @@ -782,68 +782,68 @@ showEvent :: Maybe String showEvent verbose ciM = \case Types.ModuleDeployed ref -> - verboseOrNothing $ printf "module '%s' deployed." (show ref) + verboseOrNothing $ printf "module '%s' deployed" (show ref) Types.ContractInitialized{..} -> verboseOrNothing $ [i|initialized contract '#{ecAddress}' using init function '#{ecInitName}' from module '#{ecRef}' |] - <> [i|with #{showCcd ecAmount}.\n#{showLoggedEvents ecEvents}|] + <> [i|with #{showCcd ecAmount}\n#{showLoggedEvents ecEvents}|] Types.Updated{..} -> verboseOrNothing $ [i|sent message to function '#{euReceiveName}' with #{showParameter euReceiveName euMessage} and #{showCcd euAmount} |] - <> [i|from #{showAddress euInstigator} to #{showAddress $ Types.AddressContract euAddress}.\n|] + <> [i|from #{showAddress euInstigator} to #{showAddress $ Types.AddressContract euAddress}\n|] <> [i|#{showLoggedEvents euEvents}|] Types.Transferred{..} -> - verboseOrNothing $ printf "transferred %s from %s to %s." (showCcd etAmount) (showAddress etFrom) (showAddress etTo) + verboseOrNothing $ printf "transferred %s from %s to %s" (showCcd etAmount) (showAddress etFrom) (showAddress etTo) Types.AccountCreated addr -> - verboseOrNothing $ printf "account '%s' created." (show addr) + verboseOrNothing $ printf "account '%s' created" (show addr) Types.CredentialDeployed{..} -> - verboseOrNothing $ printf "credential with registration '%s' deployed onto account '%s'." (show ecdRegId) (show ecdAccount) + verboseOrNothing $ printf "credential with registration '%s' deployed onto account '%s'" (show ecdRegId) (show ecdAccount) Types.BakerAdded{..} -> let restakeString :: String = if ebaRestakeEarnings then "Earnings are added to the stake." else "Earnings are not added to the stake." in verboseOrNothing $ printf "validator %s added, staking %s CCD. %s" (showBaker ebaBakerId ebaAccount) (Types.amountToString ebaStake) restakeString Types.BakerRemoved{..} -> - verboseOrNothing $ printf "validator %s, removed." (showBaker ebrBakerId ebrAccount) + verboseOrNothing $ printf "validator %s, removed" (showBaker ebrBakerId ebrAccount) Types.BakerStakeIncreased{..} -> - verboseOrNothing $ printf "validator %s stake increased to %s." (showBaker ebsiBakerId ebsiAccount) (showCcd ebsiNewStake) + verboseOrNothing $ printf "validator %s stake increased to %s" (showBaker ebsiBakerId ebsiAccount) (showCcd ebsiNewStake) Types.BakerStakeDecreased{..} -> - verboseOrNothing $ printf "validator %s stake decreased to %s." (showBaker ebsiBakerId ebsiAccount) (showCcd ebsiNewStake) + verboseOrNothing $ printf "validator %s stake decreased to %s" (showBaker ebsiBakerId ebsiAccount) (showCcd ebsiNewStake) Types.BakerSetRestakeEarnings{..} -> - verboseOrNothing $ printf "validator %s restake earnings %s." (showBaker ebsreBakerId ebsreAccount) (if ebsreRestakeEarnings then "set" :: String else "unset") + verboseOrNothing $ printf "validator %s restake earnings %s" (showBaker ebsreBakerId ebsreAccount) (if ebsreRestakeEarnings then "set" :: String else "unset") Types.BakerKeysUpdated{..} -> - verboseOrNothing $ printf "validator %s keys updated." (showBaker ebkuBakerId ebkuAccount) + verboseOrNothing $ printf "validator %s keys updated" (showBaker ebkuBakerId ebkuAccount) Types.CredentialsUpdated{..} -> verboseOrNothing $ [i|credentials on account #{cuAccount} have been updated.\nCredentials #{cuRemovedCredIds} have been removed, and credentials #{cuNewCredIds} have been added.\nThe new account threshold is #{cuNewThreshold}.|] Types.BakerSetOpenStatus{..} -> - verboseOrNothing $ printf "validator %s open status changed to %s." (showBaker ebsosBakerId ebsosAccount) (show ebsosOpenStatus) + verboseOrNothing $ printf "validator %s open status changed to %s" (showBaker ebsosBakerId ebsosAccount) (show ebsosOpenStatus) Types.BakerSetMetadataURL{..} -> - verboseOrNothing $ printf "validator %s URL changed to %s." (showBaker ebsmuBakerId ebsmuAccount) (show ebsmuMetadataURL) + verboseOrNothing $ printf "validator %s URL changed to %s" (showBaker ebsmuBakerId ebsmuAccount) (show ebsmuMetadataURL) Types.BakerSetTransactionFeeCommission{..} -> - verboseOrNothing $ printf "validator %s changed transaction fee commission to %s." (showBaker ebstfcBakerId ebstfcAccount) (show ebstfcTransactionFeeCommission) + verboseOrNothing $ printf "validator %s changed transaction fee commission to %s" (showBaker ebstfcBakerId ebstfcAccount) (show ebstfcTransactionFeeCommission) Types.BakerSetBakingRewardCommission{..} -> - verboseOrNothing $ printf "validator %s changed block reward commission to %s." (showBaker ebsbrcBakerId ebsbrcAccount) (show ebsbrcBakingRewardCommission) + verboseOrNothing $ printf "validator %s changed block reward commission to %s" (showBaker ebsbrcBakerId ebsbrcAccount) (show ebsbrcBakingRewardCommission) Types.BakerSetFinalizationRewardCommission{..} -> - verboseOrNothing $ printf "validator %s changed finalization reward commission to %s." (showBaker ebsfrcBakerId ebsfrcAccount) (show ebsfrcFinalizationRewardCommission) + verboseOrNothing $ printf "validator %s changed finalization reward commission to %s" (showBaker ebsfrcBakerId ebsfrcAccount) (show ebsfrcFinalizationRewardCommission) Types.DelegationStakeIncreased{..} -> - verboseOrNothing $ printf "delegator %s stake increased to %s." (showDelegator edsiDelegatorId edsiAccount) (showCcd edsiNewStake) + verboseOrNothing $ printf "delegator %s stake increased to %s" (showDelegator edsiDelegatorId edsiAccount) (showCcd edsiNewStake) Types.DelegationStakeDecreased{..} -> - verboseOrNothing $ printf "delegator %s stake decreased to %s." (showDelegator edsdDelegatorId edsdAccount) (showCcd edsdNewStake) + verboseOrNothing $ printf "delegator %s stake decreased to %s" (showDelegator edsdDelegatorId edsdAccount) (showCcd edsdNewStake) Types.DelegationSetRestakeEarnings{..} -> - verboseOrNothing $ printf "delegator %s restake earnings changed to %s." (showDelegator edsreDelegatorId edsreAccount) (show edsreRestakeEarnings) + verboseOrNothing $ printf "delegator %s restake earnings changed to %s" (showDelegator edsreDelegatorId edsreAccount) (show edsreRestakeEarnings) Types.DelegationSetDelegationTarget{..} -> - verboseOrNothing $ printf "delegator %s delegation target changed to %s." (showDelegator edsdtDelegatorId edsdtAccount) (showDelegationTarget edsdtDelegationTarget) + verboseOrNothing $ printf "delegator %s delegation target changed to %s" (showDelegator edsdtDelegatorId edsdtAccount) (showDelegationTarget edsdtDelegationTarget) Types.DelegationAdded{..} -> - verboseOrNothing $ printf "delegator %s added." (showDelegator edaDelegatorId edaAccount) + verboseOrNothing $ printf "delegator %s added" (showDelegator edaDelegatorId edaAccount) Types.DelegationRemoved{..} -> - verboseOrNothing $ printf "delegator %s removed." (showDelegator edrDelegatorId edrAccount) - Types.CredentialKeysUpdated cid -> verboseOrNothing $ printf "credential keys updated for credential with credId %s." (show cid) - Types.NewEncryptedAmount{..} -> verboseOrNothing $ printf "shielded amount received on account '%s' with index '%s'." (show neaAccount) (show neaNewIndex) - Types.EncryptedAmountsRemoved{..} -> verboseOrNothing $ printf "shielded amounts removed on account '%s' up to index '%s' with a resulting self shielded amount of '%s'." (show earAccount) (show earUpToIndex) (show earNewAmount) - Types.AmountAddedByDecryption{..} -> verboseOrNothing $ printf "transferred %s from the shielded balance to the public balance on account '%s'." (showCcd aabdAmount) (show aabdAccount) - Types.EncryptedSelfAmountAdded{..} -> verboseOrNothing $ printf "transferred %s from the public balance to the shielded balance on account '%s' with a resulting self shielded balance of '%s'." (showCcd eaaAmount) (show eaaAccount) (show eaaNewAmount) + verboseOrNothing $ printf "delegator %s removed" (showDelegator edrDelegatorId edrAccount) + Types.CredentialKeysUpdated cid -> verboseOrNothing $ printf "credential keys updated for credential with credId %s" (show cid) + Types.NewEncryptedAmount{..} -> verboseOrNothing $ printf "shielded amount received on account '%s' with index '%s'" (show neaAccount) (show neaNewIndex) + Types.EncryptedAmountsRemoved{..} -> verboseOrNothing $ printf "shielded amounts removed on account '%s' up to index '%s' with a resulting self shielded amount of '%s'" (show earAccount) (show earUpToIndex) (show earNewAmount) + Types.AmountAddedByDecryption{..} -> verboseOrNothing $ printf "transferred %s from the shielded balance to the public balance on account '%s'" (showCcd aabdAmount) (show aabdAccount) + Types.EncryptedSelfAmountAdded{..} -> verboseOrNothing $ printf "transferred %s from the public balance to the shielded balance on account '%s' with a resulting self shielded balance of '%s'" (showCcd eaaAmount) (show eaaAccount) (show eaaNewAmount) Types.UpdateEnqueued{..} -> verboseOrNothing $ printf "Enqueued chain update, effective at %s:\n%s" (showTimeFormatted (timeFromTransactionExpiryTime ueEffectiveTime)) (show uePayload) Types.TransferredWithSchedule{..} -> - verboseOrNothing $ printf "Sent transfer with schedule %s." (intercalate ", " . map (\(a, b) -> showTimeFormatted (Time.timestampToUTCTime a) ++ ": " ++ showCcd b) $ etwsAmount) + verboseOrNothing $ printf "Sent transfer with schedule %s" (intercalate ", " . map (\(a, b) -> showTimeFormatted (Time.timestampToUTCTime a) ++ ": " ++ showCcd b) $ etwsAmount) Types.DataRegistered{} -> verboseOrNothing [i|Registered data on chain.|] Types.TransferMemo{..} -> @@ -904,10 +904,10 @@ showEvent verbose ciM = \case showLoggedEvents :: [Wasm.ContractEvent] -> String showLoggedEvents [] = "No contract events were emitted." showLoggedEvents evs = - [i|#{length evs} contract #{if length evs > 1 then "events were" else ("event was" :: String)} emitted|] + [i|#{length evs} contract events were emitted|] <> ( if isNothing eventSchemaM then [i| but no event schema was provided nor found in the contract module. |] - else [i|, of which #{length $ filter isRight $ map showContractEvent evs} #{if (length $ filter isRight $ map showContractEvent evs) > 1 then "were" else ("was" :: String)} successfully parsed. |] + else [i|, of which #{length $ filter isLeft $ map showContractEvent evs} were succesfully parsed. |] ) <> [i|Got:\n|] <> intercalate "\n" (map fromEither (map showContractEvent evs)) @@ -920,7 +920,7 @@ showEvent verbose ciM = \case eventSchemaM = getEventSchema =<< ciM -- Show a string representation of the contract event. -- Returns @Right@ containing a JSON representation of the event if a schema was present - -- and the event could be successfully parsed using it. + -- and the event could be succesfully parsed using it. -- Returns @Left@ containing a hexadecimal representation of the raw event data otherwise. showContractEvent :: Wasm.ContractEvent -> Either String String showContractEvent ce@(Wasm.ContractEvent bs) = case toJSONString eventSchemaM bs of From bbdda227020d3a891041cfed5aa3d2a56a726189 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 2 May 2024 23:17:33 +0300 Subject: [PATCH 04/22] Add confirmations --- src/Concordium/Client/Commands.hs | 17 ++++++++++ src/Concordium/Client/Runner.hs | 53 ++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 57995b10..6cc19405 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -189,6 +189,10 @@ data TransactionCmd { tsFile :: !FilePath, tsInteractionOpts :: !InteractionOpts } + | TransactionAddSignature + { tsFile :: !FilePath, + tsInteractionOpts :: !InteractionOpts + } | TransactionStatus { tsHash :: !Text, -- | Path to a contract schema, used to display the transaction event info. @@ -707,6 +711,7 @@ transactionCmds = <$> hsubparser ( transactionSignAndSubmitCmd <> transactionSubmitCmd + <> transactionAddSignatureCmd <> transactionStatusCmd <> transactionSendCcdCmd <> transactionWithScheduleCmd @@ -742,6 +747,18 @@ transactionSubmitCmd = (progDesc "Parse signed transaction and send it to the node.") ) +transactionAddSignatureCmd :: Mod CommandFields TransactionCmd +transactionAddSignatureCmd = + command + "add-signature" + ( info + ( TransactionAddSignature + <$> strArgument (metavar "FILE" <> help "File containing a signed transaction in JSON format.") + <*> interactionOptsParser + ) + (progDesc "Adds a signature to the transaction in the file.") + ) + transactionDeployCredentialCmd :: Mod CommandFields TransactionCmd transactionDeployCredentialCmd = command diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 2ee6ee56..33a1a4ab 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -657,7 +657,11 @@ processTransactionCmd action baseCfgDir verbose backend = -- TODO Ensure that the "nonce" field is optional in the payload. source <- handleReadFile BSL.readFile fname - -- TODO Print transaction details and ask for confirmation if (ioConfirm intOpts) + -- TODO Print transaction details + + when (ioConfirm intOpts) $ do + confirmed <- askConfirmation $ Just "Do you want to send the transaction on chain? " + unless confirmed exitTransactionCancelled withClient backend $ do tx <- processTransaction source @@ -667,21 +671,27 @@ processTransactionCmd action baseCfgDir verbose backend = tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] TransactionSubmit fname intOpts -> do - jsonFileContent <- liftIO $ BSL8.readFile fname + fileContent <- liftIO $ BSL8.readFile fname + + -- Decode JSON file content into a AccountTransaction + let accountTransaction :: Types.AccountTransaction + accountTransaction = case decode fileContent of + Just item -> item + Nothing -> error "Failed to decode file content into AccountTransaction type" -- TODO: Properly decode and display, especially the payload part - -- TODO: Ask for confirmation if (ioConfirm intOpts) - logInfo [[i| #{jsonFileContent}.|]] + logInfo ["Transaction in file: "] + logInfo [[i| #{showPrettyJSON accountTransaction}.|]] - -- Decode JSON file content into a BareBlockItem - let bareBlockItem :: Types.BareBlockItem - bareBlockItem = case decode jsonFileContent of - Just item -> item - Nothing -> error "Failed to decode JSON file content" + when (ioConfirm intOpts) $ do + confirmed <- askConfirmation $ Just "Do you want to send the transaction on chain? " + unless confirmed exitTransactionCancelled + + let tx = Types.NormalTransaction accountTransaction withClient backend $ do -- Send transaction on chain - sbiRes <- sendBlockItem bareBlockItem + sbiRes <- sendBlockItem tx let res = case sbiRes of StatusOk resp -> Right resp StatusNotOk (status, err) -> Left [i|GRPC response with status '#{status}': #{err}|] @@ -691,11 +701,29 @@ processTransactionCmd action baseCfgDir verbose backend = case res of Left err -> logFatal ["Transaction not accepted by the node: " <> err] Right _ -> do - let hash = getBlockItemHash bareBlockItem + let hash = getBlockItemHash tx logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] + TransactionAddSignature fname _intOpts -> do + fileContent <- liftIO $ BSL8.readFile fname + + -- Decode JSON file content into a AccountTransaction + let accountTransaction :: Types.AccountTransaction + accountTransaction = case decode fileContent of + Just item -> item + Nothing -> error "Failed to decode file content into AccountTransaction type" + + -- TODO: Properly decode and display, especially the payload part + logInfo ["Transaction in file: "] + logInfo [[i| #{showPrettyJSON accountTransaction}.|]] + + let _tx = Types.NormalTransaction accountTransaction + -- TODO: Use _tx and _intOpts + -- TODO: generate signature and write into file + + logSuccess [[i|Added signature successfully to the transaction in the file '#{fname}'|]] TransactionDeployCredential fname intOpts -> do source <- handleReadFile BSL.readFile fname withClient backend $ do @@ -1599,12 +1627,13 @@ sendAndTailTransaction verbose txCfg pl intOpts = do else return Nothing else do logInfo [[i| Write signed transaction to file. Don't send it to the node.|]] - -- FIXME: pass in output file via flag. + -- TODO: pass in output file via flag. let outFile = "./transaction.json" let txJson = AE.encode tx success <- liftIO $ handleWriteFile BSL.writeFile PromptBeforeOverwrite verbose outFile txJson when success $ logSuccess [[i|Wrote transaction successfully to the file '#{outFile}'|]] + -- TODO: write error if not failing to write to file return Nothing -- | Continuously query and display transaction status until the transaction is finalized. From 9b1aff5778b86d0a63c4f3571e82280d15ddc5c8 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 3 May 2024 18:52:45 +0300 Subject: [PATCH 05/22] Add signing of partially signed transaction --- src/Concordium/Client/Commands.hs | 4 +- src/Concordium/Client/Runner.hs | 155 ++++++++++++++++++++++-------- 2 files changed, 115 insertions(+), 44 deletions(-) diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 6cc19405..7c2efb91 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -191,7 +191,7 @@ data TransactionCmd } | TransactionAddSignature { tsFile :: !FilePath, - tsInteractionOpts :: !InteractionOpts + signers :: !(Maybe Text) } | TransactionStatus { tsHash :: !Text, @@ -754,7 +754,7 @@ transactionAddSignatureCmd = ( info ( TransactionAddSignature <$> strArgument (metavar "FILE" <> help "File containing a signed transaction in JSON format.") - <*> interactionOptsParser + <*> optional (strOption (long "signers" <> metavar "SIGNERS" <> help "Specification of which (local) keys to sign with. Example: \"0:1,0:2,3:0,3:1\" specifies that credential holder 0 signs with keys 1 and 2, while credential holder 3 signs with keys 0 and 1")) ) (progDesc "Adds a signature to the transaction in the file.") ) diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 33a1a4ab..615fbf43 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -706,7 +706,7 @@ processTransactionCmd action baseCfgDir verbose backend = when (ioTail intOpts) $ do tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] - TransactionAddSignature fname _intOpts -> do + TransactionAddSignature fname signers -> do fileContent <- liftIO $ BSL8.readFile fname -- Decode JSON file content into a AccountTransaction @@ -719,9 +719,24 @@ processTransactionCmd action baseCfgDir verbose backend = logInfo ["Transaction in file: "] logInfo [[i| #{showPrettyJSON accountTransaction}.|]] - let _tx = Types.NormalTransaction accountTransaction - -- TODO: Use _tx and _intOpts - -- TODO: generate signature and write into file + baseCfg <- getBaseConfig baseCfgDir verbose + + let header = Types.atrHeader accountTransaction + let encPayload = Types.atrPayload accountTransaction + let signerAccountAddress = Types.thSender header + let signerAccountAddressText = Text.pack $ show signerAccountAddress + + -- TODO: better name than `getAccountCfgFromTxOpts2` and consolidate both functions + encryptedSigningData <- getAccountCfgFromTxOpts2 baseCfg signerAccountAddressText signers + accountKeyMap <- liftIO $ failOnError $ decryptAccountKeyMapInteractive (esdKeys encryptedSigningData) Nothing Nothing + + let signatures = signEncodedTransaction encPayload header accountKeyMap + + logInfo [[i| "Signatures: " |]] + logInfo [[i| ""|]] + logInfo [[i| #{signatures}.|]] + + -- TODO: write signatures into file logSuccess [[i|Added signature successfully to the transaction in the file '#{fname}'|]] TransactionDeployCredential fname intOpts -> do @@ -777,7 +792,7 @@ processTransactionCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts liftIO $ transferTransactionConfirm ttxCfg (ioConfirm intOpts) - sendAndTailTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts TransactionSendWithSchedule receiver schedule maybeMemo txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -829,7 +844,7 @@ processTransactionCmd action baseCfgDir verbose backend = else do let intOpts = toInteractionOpts txOpts liftIO $ transferWithScheduleTransactionConfirm ttxCfg (ioConfirm intOpts) - sendAndTailTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts TransactionEncryptedTransfer txOpts receiver amount index maybeMemo -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -874,7 +889,7 @@ processTransactionCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts encryptedTransferTransactionConfirm ettCfg (ioConfirm intOpts) - sendAndTailTransaction_ verbose txCfg payload intOpts + signAndProcessTransaction_ verbose txCfg payload intOpts TransactionRegisterData file txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose rdCfg <- getRegisterDataTransactionCfg baseCfg txOpts file @@ -893,7 +908,7 @@ processTransactionCmd action baseCfgDir verbose backend = let pl = registerDataTransactionPayload rdCfg withClient backend $ do - mTsr <- sendAndTailTransaction verbose txCfg (Types.encodePayload pl) intOpts + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts let extractDataRegistered = extractFromTsr $ \case Types.DataRegistered rd -> Just rd _ -> Nothing @@ -1059,6 +1074,51 @@ warnSuspiciousExpiry expiryArg now logWarn ["expiration time is in more than one hour"] | otherwise = return () +-- | Get accountCfg from the config folder and return EncryptedSigningData or logFatal if the keys are not provided in txOpts. +getAccountCfgFromTxOpts2 :: BaseConfig -> Text -> Maybe Text -> IO EncryptedSigningData +getAccountCfgFromTxOpts2 baseCfg signerAccountAddressText signers = do + let chosenKeysMaybe :: Maybe (Map.Map ID.CredentialIndex [ID.KeyIndex]) = case signers of + Nothing -> Nothing + Just t -> + Just $ + let insertKey c k acc = case Map.lookup c acc of + Nothing -> Map.insert c [k] acc + Just x -> Map.insert c ([k] ++ x) acc + in foldl' (\acc (c, k) -> insertKey c k acc) Map.empty $ fmap ((\(p1, p2) -> (read . Text.unpack $ p1, read . Text.unpack $ Text.drop 1 p2)) . Text.breakOn ":") $ Text.split (== ',') t + + (_, accCfg) <- getAccountConfig (Just signerAccountAddressText) baseCfg Nothing Nothing Nothing AssumeInitialized + + let keys = acKeys accCfg + + case chosenKeysMaybe of + Nothing -> return EncryptedSigningData{esdKeys = keys, esdAddress = acAddr accCfg, esdEncryptionKey = acEncryptionKey accCfg} + Just chosenKeys -> do + let newKeys = Map.intersection keys chosenKeys + let filteredKeys = + Map.mapWithKey + ( \c m -> + Map.filterWithKey + ( \k _ -> case Map.lookup c chosenKeys of + Nothing -> False + Just keyList -> elem k keyList + ) + m + ) + newKeys + _ <- + Map.traverseWithKey + ( \c keyIndices -> case Map.lookup c newKeys of + Nothing -> logFatal ["No credential holder with index " ++ (show c) ++ "."] + Just credHolderMap -> do + let warnIfMissingKey keyIndex = case Map.lookup keyIndex credHolderMap of + Nothing -> logFatal ["No key with index " ++ (show keyIndex) ++ " for credential holder " ++ (show c) ++ "."] + Just _ -> return () -- Key found, do nothing. + -- We could add the key to a map in this case, replacing the intersection and mapWithKey steps above. + mapM_ warnIfMissingKey keyIndices + ) + chosenKeys + return EncryptedSigningData{esdKeys = filteredKeys, esdAddress = acAddr accCfg, esdEncryptionKey = acEncryptionKey accCfg} + -- | Get accountCfg from the config folder and return EncryptedSigningData or logFatal if the keys are not provided in txOpts. getAccountCfgFromTxOpts :: BaseConfig -> TransactionOpts energyOrMaybe -> IO EncryptedSigningData getAccountCfgFromTxOpts baseCfg txOpts = do @@ -1543,7 +1603,7 @@ startTransaction txCfg pl confirmNonce maybeAccKeys = do Just acKeys' -> return acKeys' Nothing -> liftIO $ failOnError $ decryptAccountKeyMapInteractive esdKeys Nothing Nothing let sender = applyAlias tcAlias naAddr - let tx = signEncodedTransaction pl sender energy nonce expiry accountKeyMap + let tx = formatAndSignTransaction pl sender energy nonce expiry accountKeyMap when (isJust tcAlias) $ logInfo [[i|Using the alias #{sender} as the sender of the transaction instead of #{naAddr}.|]] @@ -1574,7 +1634,7 @@ getNonce sender nonce confirm = Just v -> return v -- | Send a transaction and optionally tail it (see 'tailTransaction' below). -sendAndTailTransaction_ :: +signAndProcessTransaction_ :: (MonadIO m, MonadFail m) => -- | Whether the output should be verbose Bool -> @@ -1585,13 +1645,13 @@ sendAndTailTransaction_ :: -- | How interactive should sending and tailing be InteractionOpts -> ClientMonad m () -sendAndTailTransaction_ verbose txCfg pl intOpts = void $ sendAndTailTransaction verbose txCfg pl intOpts +signAndProcessTransaction_ verbose txCfg pl intOpts = void $ signAndProcessTransaction verbose txCfg pl intOpts --- | Sign a transaction and either send it to the node or write it to a file. +-- | Sign a transaction and process transaction by either send it to the node or write it to a file. -- If send to the node, optionally tail it (see 'tailTransaction' below). -- If tailed, it returns the TransactionStatusResult of the finalized status, -- otherwise the return value is @Nothing@. -sendAndTailTransaction :: +signAndProcessTransaction :: (MonadIO m, MonadFail m) => -- | Whether the output should be verbose Bool -> @@ -1602,7 +1662,7 @@ sendAndTailTransaction :: -- | How interactive should sending and tailing be InteractionOpts -> ClientMonad m (Maybe TransactionStatusResult) -sendAndTailTransaction verbose txCfg pl intOpts = do +signAndProcessTransaction verbose txCfg pl intOpts = do tx <- startTransaction txCfg pl (ioConfirm intOpts) Nothing if (ioSubmit intOpts) @@ -1630,7 +1690,7 @@ sendAndTailTransaction verbose txCfg pl intOpts = do -- TODO: pass in output file via flag. let outFile = "./transaction.json" - let txJson = AE.encode tx + let txJson = AE.encodePretty tx success <- liftIO $ handleWriteFile BSL.writeFile PromptBeforeOverwrite verbose outFile txJson when success $ logSuccess [[i|Wrote transaction successfully to the file '#{outFile}'|]] -- TODO: write error if not failing to write to file @@ -1838,7 +1898,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts liftIO $ credentialUpdateKeysTransactionConfirm aukCfg (ioConfirm intOpts) - sendAndTailTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts AccountUpdateCredentials cdisFile removeCidsFile newThreshold txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose @@ -1868,7 +1928,7 @@ processAccountCmd action baseCfgDir verbose backend = auctcNewThreshold = newThreshold } liftIO $ accountUpdateCredentialsTransactionConfirm aucCfg (ioConfirm intOpts) - sendAndTailTransaction_ verbose txCfg epayload intOpts + signAndProcessTransaction_ verbose txCfg epayload intOpts AccountEncrypt{..} -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -1885,7 +1945,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts aeTransactionOpts accountEncryptTransactionConfirm aetxCfg (ioConfirm intOpts) - withClient backend $ sendAndTailTransaction_ verbose txCfg pl intOpts + withClient backend $ signAndProcessTransaction_ verbose txCfg pl intOpts AccountDecrypt{..} -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -1917,7 +1977,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts adTransactionOpts accountDecryptTransactionConfirm adtxCfg (ioConfirm intOpts) - sendAndTailTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts AccountShowAlias addrOrName alias -> do baseCfg <- getBaseConfig baseCfgDir verbose case getAccountAddress (bcAccountNameMap baseCfg) addrOrName of @@ -1955,7 +2015,7 @@ processModuleCmd action baseCfgDir verbose backend = let pl = moduleDeployTransactionPayload mdCfg withClient backend $ do - mTsr <- sendAndTailTransaction verbose txCfg (Types.encodePayload pl) intOpts + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts case extractModRef mTsr of Nothing -> return () Just (Left err) -> logFatal ["module deployment failed:", err] @@ -2207,7 +2267,7 @@ processContractCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts let pl = contractInitTransactionPayload ciCfg withClient backend $ do - mTsr <- sendAndTailTransaction verbose txCfg (Types.encodePayload pl) intOpts + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts case extractContractAddress mTsr of Nothing -> return () Just (Left err) -> logFatal ["contract initialisation failed:", err] @@ -2258,7 +2318,7 @@ processContractCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts let pl = contractUpdateTransactionPayload cuCfg withClient backend $ do - mTsr <- sendAndTailTransaction verbose txCfg (Types.encodePayload pl) intOpts + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts case extractUpdate mTsr of Nothing -> return () Just (Left err) -> logFatal ["updating contract instance failed:", err] @@ -3003,7 +3063,7 @@ processBakerConfigureCmd baseCfgDir verbose backend txOpts isBakerConfigure cbCa withClient backend $ do when isBakerConfigure $ warnAboutMissingAddBakerParameters txCfg mapM_ (warnAboutBadCapital txCfg) cbCapital - result <- sendAndTailTransaction verbose txCfg pl intOpts + result <- signAndProcessTransaction verbose txCfg pl intOpts events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3250,7 +3310,7 @@ processBakerAddCmd baseCfgDir verbose backend txOpts abBakingStake abRestakeEarn (bakerKeys, txCfg, pl) <- transactionForBakerAdd (ioConfirm intOpts) withClient backend $ do warnAboutBadCapital txCfg abBakingStake - result <- sendAndTailTransaction verbose txCfg pl intOpts + result <- signAndProcessTransaction verbose txCfg pl intOpts events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3410,7 +3470,7 @@ processBakerSetKeysCmd baseCfgDir verbose backend txOpts inputKeysFile outputKey let intOpts = toInteractionOpts txOpts (bakerKeys, txCfg, pl) <- transactionForBakerSetKeys (ioConfirm intOpts) withClient backend $ do - result <- sendAndTailTransaction verbose txCfg pl intOpts + result <- signAndProcessTransaction verbose txCfg pl intOpts events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3527,7 +3587,7 @@ processBakerRemoveCmd baseCfgDir verbose backend txOpts = do (txCfg, pl) <- transactionForBakerRemove (ioConfirm intOpts) withClient backend $ do liftIO warnAboutRemoving - sendAndTailTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts where warnAboutRemoving = do cooldownDate <- withClient backend $ do @@ -3576,7 +3636,7 @@ processBakerUpdateStakeBeforeP4Cmd baseCfgDir verbose backend txOpts ubsStake = (txCfg, pl) <- transactionForBakerUpdateStake (ioConfirm intOpts) withClient backend $ do warnAboutBadCapital txCfg ubsStake - sendAndTailTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts where warnAboutBadCapital txCfg capital = do let senderAddr = naAddr . esdAddress . tcEncryptedSigningData $ txCfg @@ -3665,7 +3725,7 @@ processBakerUpdateRestakeCmd baseCfgDir verbose backend txOpts ubreRestakeEarnin let intOpts = toInteractionOpts txOpts (txCfg, pl) <- transactionForBakerUpdateRestake (ioConfirm intOpts) withClient backend $ do - sendAndTailTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts where transactionForBakerUpdateRestake confirm = do baseCfg <- getBaseConfig baseCfgDir verbose @@ -3858,7 +3918,7 @@ processDelegatorConfigureCmd baseCfgDir verbose backend txOpts cdCapital cdResta withClient backend $ do warnInOldProtocol mapM_ (warnAboutBadCapital txCfg) cdCapital - result <- sendAndTailTransaction verbose txCfg pl intOpts + result <- signAndProcessTransaction verbose txCfg pl intOpts warnAboutFailedResult result where warnInOldProtocol = do @@ -4553,11 +4613,11 @@ encodeAndSignTransaction :: Types.TransactionExpiryTime -> AccountKeyMap -> Types.BareBlockItem -encodeAndSignTransaction txPayload = signEncodedTransaction (Types.encodePayload txPayload) +encodeAndSignTransaction txPayload = formatAndSignTransaction (Types.encodePayload txPayload) --- | Sign an encoded transaction payload and a configuration into a "normal" transaction, +-- | Format the header of the transaction and sign it together with the encoded transaction payload and return a "normal" transaction, -- which is ready to be sent. -signEncodedTransaction :: +formatAndSignTransaction :: Types.EncodedPayload -> Types.AccountAddress -> Types.Energy -> @@ -4565,15 +4625,26 @@ signEncodedTransaction :: Types.TransactionExpiryTime -> AccountKeyMap -> Types.BareBlockItem -signEncodedTransaction encPayload sender energy nonce expiry accKeys = +formatAndSignTransaction encPayload sender energy nonce expiry accKeys = + signEncodedTransaction encPayload header accKeys + where + header = + Types.TransactionHeader + { thSender = sender, + thPayloadSize = Types.payloadSize encPayload, + thNonce = nonce, + thEnergyAmount = energy, + thExpiry = expiry + } + +-- | Sign an encoded transaction payload, and header with the account key map +-- and return a "normal" transaction, which is ready to be sent. +signEncodedTransaction :: + Types.EncodedPayload -> + Types.TransactionHeader -> + AccountKeyMap -> + Types.BareBlockItem +signEncodedTransaction encPayload header accKeys = Types.NormalTransaction $ - let header = - Types.TransactionHeader - { thSender = sender, - thPayloadSize = Types.payloadSize encPayload, - thNonce = nonce, - thEnergyAmount = energy, - thExpiry = expiry - } - keys = Map.toList $ fmap Map.toList accKeys + let keys = Map.toList $ fmap Map.toList accKeys in Types.signTransaction keys header encPayload From 4e1625ab732589b0a0d1091795fa31de45397a99 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Sun, 5 May 2024 12:50:39 +0300 Subject: [PATCH 06/22] Save the union of the signature maps to file --- deps/concordium-base | 2 +- src/Concordium/Client/Runner.hs | 55 +++++++++++++++++++++------------ stack.yaml | 1 + 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 7308605c..e9e71c16 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 7308605c14db5f5e66b426f8596d699e6bfae822 +Subproject commit e9e71c168600edecabe02be0974719665a7cd55a diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 615fbf43..da3be2b0 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -673,7 +673,7 @@ processTransactionCmd action baseCfgDir verbose backend = TransactionSubmit fname intOpts -> do fileContent <- liftIO $ BSL8.readFile fname - -- Decode JSON file content into a AccountTransaction + -- Decode JSON file content into an AccountTransaction let accountTransaction :: Types.AccountTransaction accountTransaction = case decode fileContent of Just item -> item @@ -709,36 +709,49 @@ processTransactionCmd action baseCfgDir verbose backend = TransactionAddSignature fname signers -> do fileContent <- liftIO $ BSL8.readFile fname - -- Decode JSON file content into a AccountTransaction + -- Decode JSON file content into an AccountTransaction let accountTransaction :: Types.AccountTransaction accountTransaction = case decode fileContent of Just item -> item Nothing -> error "Failed to decode file content into AccountTransaction type" + -- Display transaction from the file -- TODO: Properly decode and display, especially the payload part logInfo ["Transaction in file: "] logInfo [[i| #{showPrettyJSON accountTransaction}.|]] + -- Extract accountKeyMap to be used to sign the transaction baseCfg <- getBaseConfig baseCfgDir verbose - let header = Types.atrHeader accountTransaction - let encPayload = Types.atrPayload accountTransaction - let signerAccountAddress = Types.thSender header - let signerAccountAddressText = Text.pack $ show signerAccountAddress + let signerAccountAddressText = Text.pack $ show (Types.thSender header) + + -- TODO: should we check if the `nonce` still makes sense as read from the file vs the one on-chain. + -- (and throw error if additional txs have been sent by account meanwhile meaning the account nonce is not valid anymore). -- TODO: better name than `getAccountCfgFromTxOpts2` and consolidate both functions encryptedSigningData <- getAccountCfgFromTxOpts2 baseCfg signerAccountAddressText signers accountKeyMap <- liftIO $ failOnError $ decryptAccountKeyMapInteractive (esdKeys encryptedSigningData) Nothing Nothing - let signatures = signEncodedTransaction encPayload header accountKeyMap + -- Sign transaction and extract the signature map B (new signatures to be added) + let encPayload = Types.atrPayload accountTransaction + let transactionB = signEncodedTransaction encPayload header accountKeyMap + let sigBMap = Types.tsSignatures (Types.atrSignature transactionB) + + -- Extract the signature map A (original signatures as stored in the file) + let sigAMap = Types.tsSignatures (Types.atrSignature accountTransaction) - logInfo [[i| "Signatures: " |]] - logInfo [[i| ""|]] - logInfo [[i| #{signatures}.|]] + -- Create the union of the signature map A and the signature map B + let unionSignaturesMap = Map.unionWith Map.union sigAMap sigBMap - -- TODO: write signatures into file + -- Create final signed transaction including signtures A and B + let finalTransaction = AE.encodePretty accountTransaction{Types.atrSignature = Types.TransactionSignature unionSignaturesMap} - logSuccess [[i|Added signature successfully to the transaction in the file '#{fname}'|]] + -- TODO: remove the extra confirmation + -- Write finalTransaction to file + success <- liftIO $ handleWriteFile BSL.writeFile PromptBeforeOverwrite verbose fname finalTransaction + if success + then liftIO $ logSuccess [[i|Added signature successfully to the transaction in the file '#{fname}'|]] + else liftIO $ logError [[i|Failed to write signature to the file '#{fname}'|]] TransactionDeployCredential fname intOpts -> do source <- handleReadFile BSL.readFile fname withClient backend $ do @@ -1692,8 +1705,11 @@ signAndProcessTransaction verbose txCfg pl intOpts = do let txJson = AE.encodePretty tx success <- liftIO $ handleWriteFile BSL.writeFile PromptBeforeOverwrite verbose outFile txJson - when success $ logSuccess [[i|Wrote transaction successfully to the file '#{outFile}'|]] - -- TODO: write error if not failing to write to file + + if success + then liftIO $ logSuccess [[i|Wrote transaction successfully to the file '#{outFile}'|]] + else liftIO $ logError [[i|Failed to write transaction to the file '#{outFile}'|]] + return Nothing -- | Continuously query and display transaction status until the transaction is finalized. @@ -4626,7 +4642,7 @@ formatAndSignTransaction :: AccountKeyMap -> Types.BareBlockItem formatAndSignTransaction encPayload sender energy nonce expiry accKeys = - signEncodedTransaction encPayload header accKeys + Types.NormalTransaction $ signEncodedTransaction encPayload header accKeys where header = Types.TransactionHeader @@ -4638,13 +4654,12 @@ formatAndSignTransaction encPayload sender energy nonce expiry accKeys = } -- | Sign an encoded transaction payload, and header with the account key map --- and return a "normal" transaction, which is ready to be sent. +-- and return a "normal" AccountTransaction. signEncodedTransaction :: Types.EncodedPayload -> Types.TransactionHeader -> AccountKeyMap -> - Types.BareBlockItem + Types.AccountTransaction signEncodedTransaction encPayload header accKeys = - Types.NormalTransaction $ - let keys = Map.toList $ fmap Map.toList accKeys - in Types.signTransaction keys header encPayload + let keys = Map.toList $ fmap Map.toList accKeys + in Types.signTransaction keys header encPayload diff --git a/stack.yaml b/stack.yaml index deaf9504..179176f4 100644 --- a/stack.yaml +++ b/stack.yaml @@ -18,6 +18,7 @@ # resolver: ./custom-snapshot.yaml # resolver: https://example.com/snapshots/2018-01-01.yaml resolver: lts-22.9 +system-ghc: true # User packages to be built. # Various formats can be used as shown in the example below. From f641d9be62e70fd6a90726020e1659fc210eb311 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 6 May 2024 19:52:37 +0300 Subject: [PATCH 07/22] Add decoding of payload --- src/Concordium/Client/Runner.hs | 59 ++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index da3be2b0..73c7ebc9 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -674,15 +674,30 @@ processTransactionCmd action baseCfgDir verbose backend = fileContent <- liftIO $ BSL8.readFile fname -- Decode JSON file content into an AccountTransaction - let accountTransaction :: Types.AccountTransaction - accountTransaction = case decode fileContent of - Just item -> item - Nothing -> error "Failed to decode file content into AccountTransaction type" + accountTransaction <- case decode fileContent of + Just item -> return item + Nothing -> logFatal ["Failed to decode file content into AccountTransaction type"] - -- TODO: Properly decode and display, especially the payload part + -- Display the transaction from the JSON file logInfo ["Transaction in file: "] logInfo [[i| #{showPrettyJSON accountTransaction}.|]] + -- Display the decoded payload from the transaction + let encPayload = Types.atrPayload accountTransaction + + pv <- withClient backend $ do + cs <- getResponseValueOrDie =<< getConsensusInfo + return $ Queries.csProtocolVersion cs + + -- TODO: Use the above `pv` instead of `Types.SP6`. + decoded_payload <- case Types.decodePayload Types.SP6 (Types.payloadSize encPayload) encPayload of + Right payload -> return payload + Left err -> logFatal ["Transaction not accepted by the node: " <> err] + logInfo [[i| Decoded payload:|]] + logInfo [[i| #{showPrettyJSON decoded_payload}.|]] + + -- TODO: to further decode the `message` in contract update or init transactions, we need a schema. + when (ioConfirm intOpts) $ do confirmed <- askConfirmation $ Just "Do you want to send the transaction on chain? " unless confirmed exitTransactionCancelled @@ -710,16 +725,31 @@ processTransactionCmd action baseCfgDir verbose backend = fileContent <- liftIO $ BSL8.readFile fname -- Decode JSON file content into an AccountTransaction - let accountTransaction :: Types.AccountTransaction - accountTransaction = case decode fileContent of - Just item -> item - Nothing -> error "Failed to decode file content into AccountTransaction type" + accountTransaction <- case decode fileContent of + Just item -> return item + Nothing -> logFatal ["Failed to decode file content into AccountTransaction type"] - -- Display transaction from the file - -- TODO: Properly decode and display, especially the payload part + -- Display the transaction from the JSON file logInfo ["Transaction in file: "] logInfo [[i| #{showPrettyJSON accountTransaction}.|]] + -- Display the decoded payload from the transaction + let encPayload = Types.atrPayload accountTransaction + + pv <- withClient backend $ do + cs <- getResponseValueOrDie =<< getConsensusInfo + return $ Queries.csProtocolVersion cs + + -- TODO: Use the above `pv` instead of `Types.SP6`. + + decoded_payload <- case Types.decodePayload Types.SP6 (Types.payloadSize encPayload) encPayload of + Right payload -> return payload + Left err -> logFatal ["Transaction not accepted by the node: " <> err] + logInfo [[i| Decoded payload:|]] + logInfo [[i| #{showPrettyJSON decoded_payload}.|]] + + -- TODO: to further decode the `message` in contract update or init transactions, we need a schema. + -- Extract accountKeyMap to be used to sign the transaction baseCfg <- getBaseConfig baseCfgDir verbose let header = Types.atrHeader accountTransaction @@ -733,7 +763,6 @@ processTransactionCmd action baseCfgDir verbose backend = accountKeyMap <- liftIO $ failOnError $ decryptAccountKeyMapInteractive (esdKeys encryptedSigningData) Nothing Nothing -- Sign transaction and extract the signature map B (new signatures to be added) - let encPayload = Types.atrPayload accountTransaction let transactionB = signEncodedTransaction encPayload header accountKeyMap let sigBMap = Types.tsSignatures (Types.atrSignature transactionB) @@ -1543,8 +1572,8 @@ accountUpdateCredentialsTransactionConfirm AccountUpdateCredentialsTransactionCf logInfo $ [printf "adding credentials to account %s with the following credential registration ids on account:" (showNamedAddress addr)] ++ logNewCids - ++ [printf "setting new account threshold %s" (show (auctcNewThreshold))] - ++ [printf "removing credentials with credential registration ids %s" (show (auctcRemoveCredIds))] + ++ [printf "setting new account threshold %s" (show auctcNewThreshold)] + ++ [printf "removing credentials with credential registration ids %s" (show auctcRemoveCredIds)] ++ [ printf "allowing up to %s to be spent as transaction fee" (showNrg energy), printf "transaction expires on %s" (showTimeFormatted $ timeFromTransactionExpiryTime expiry) ] @@ -1678,7 +1707,7 @@ signAndProcessTransaction :: signAndProcessTransaction verbose txCfg pl intOpts = do tx <- startTransaction txCfg pl (ioConfirm intOpts) Nothing - if (ioSubmit intOpts) + if ioSubmit intOpts then do logInfo [[i| Send signed transaction to node.|]] From 9c333082b23320ac88064070767790ee493a425f Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 7 May 2024 11:33:04 +0300 Subject: [PATCH 08/22] Move reading and displaying of account transation into separate function --- src/Concordium/Client/Runner.hs | 94 ++++++++++++++------------------- 1 file changed, 41 insertions(+), 53 deletions(-) diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 73c7ebc9..b20ff9e7 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -649,6 +649,43 @@ getTxContractInfoWithSchemas schemaFile status = do MultipleBlocksUnambiguous bhs ts -> map (,f ts) bhs MultipleBlocksAmbiguous bhts -> map (second f) bhts +-- | Read and display an `AccountTransaction` from a JSON file. +-- Returns the `AccountTransaction`. +readAndDisplayAccountTransaction :: FilePath -> Backend -> IO Types.AccountTransaction +readAndDisplayAccountTransaction fname backend = do + fileContent <- liftIO $ BSL8.readFile fname + + -- Decode JSON file content into an AccountTransaction + accountTransaction <- case decode fileContent of + Just tx -> do + -- Display the transaction from the JSON file + logInfo ["Transaction in file: "] + logInfo [[i| #{showPrettyJSON tx}.|]] + + return tx + Nothing -> logFatal ["Failed to decode file content into AccountTransaction type"] + + -- Get current protocol version + pv <- withClient backend $ do + cs <- getResponseValueOrDie =<< getConsensusInfo + return $ Queries.csProtocolVersion cs + + -- Decode the display the payload from the transaction + let encPayload = Types.atrPayload accountTransaction + + let decPayload = case Types.promoteProtocolVersion pv of + Types.SomeProtocolVersion spv -> Types.decodePayload spv (Types.payloadSize encPayload) encPayload + + case decPayload of + Right payload -> do + logInfo [[i| Decoded payload:|]] + logInfo [[i| #{showPrettyJSON payload}.|]] + Left err -> logFatal ["Decoding of payload failed: " <> err] + + -- TODO: to further decode the `message` in contract update or init transactions, we need a schema. + + return accountTransaction + -- | Process a 'transaction ...' command. processTransactionCmd :: TransactionCmd -> Maybe FilePath -> Verbose -> Backend -> IO () processTransactionCmd action baseCfgDir verbose backend = @@ -671,33 +708,8 @@ processTransactionCmd action baseCfgDir verbose backend = tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] TransactionSubmit fname intOpts -> do - fileContent <- liftIO $ BSL8.readFile fname - - -- Decode JSON file content into an AccountTransaction - accountTransaction <- case decode fileContent of - Just item -> return item - Nothing -> logFatal ["Failed to decode file content into AccountTransaction type"] - - -- Display the transaction from the JSON file - logInfo ["Transaction in file: "] - logInfo [[i| #{showPrettyJSON accountTransaction}.|]] - - -- Display the decoded payload from the transaction - let encPayload = Types.atrPayload accountTransaction - - pv <- withClient backend $ do - cs <- getResponseValueOrDie =<< getConsensusInfo - return $ Queries.csProtocolVersion cs - - -- TODO: Use the above `pv` instead of `Types.SP6`. - decoded_payload <- case Types.decodePayload Types.SP6 (Types.payloadSize encPayload) encPayload of - Right payload -> return payload - Left err -> logFatal ["Transaction not accepted by the node: " <> err] - logInfo [[i| Decoded payload:|]] - logInfo [[i| #{showPrettyJSON decoded_payload}.|]] - - -- TODO: to further decode the `message` in contract update or init transactions, we need a schema. - + accountTransaction <- readAndDisplayAccountTransaction fname backend + when (ioConfirm intOpts) $ do confirmed <- askConfirmation $ Just "Do you want to send the transaction on chain? " unless confirmed exitTransactionCancelled @@ -722,34 +734,10 @@ processTransactionCmd action baseCfgDir verbose backend = tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] TransactionAddSignature fname signers -> do - fileContent <- liftIO $ BSL8.readFile fname - - -- Decode JSON file content into an AccountTransaction - accountTransaction <- case decode fileContent of - Just item -> return item - Nothing -> logFatal ["Failed to decode file content into AccountTransaction type"] - - -- Display the transaction from the JSON file - logInfo ["Transaction in file: "] - logInfo [[i| #{showPrettyJSON accountTransaction}.|]] - - -- Display the decoded payload from the transaction + accountTransaction <- readAndDisplayAccountTransaction fname backend + -- Get encoded payload let encPayload = Types.atrPayload accountTransaction - pv <- withClient backend $ do - cs <- getResponseValueOrDie =<< getConsensusInfo - return $ Queries.csProtocolVersion cs - - -- TODO: Use the above `pv` instead of `Types.SP6`. - - decoded_payload <- case Types.decodePayload Types.SP6 (Types.payloadSize encPayload) encPayload of - Right payload -> return payload - Left err -> logFatal ["Transaction not accepted by the node: " <> err] - logInfo [[i| Decoded payload:|]] - logInfo [[i| #{showPrettyJSON decoded_payload}.|]] - - -- TODO: to further decode the `message` in contract update or init transactions, we need a schema. - -- Extract accountKeyMap to be used to sign the transaction baseCfg <- getBaseConfig baseCfgDir verbose let header = Types.atrHeader accountTransaction From 6e988fc5a4ba9da5be01dc5c55baa873d1bd5e28 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 8 May 2024 17:06:48 +0300 Subject: [PATCH 09/22] Add pasing of keys via flag for signing --- src/Concordium/Client/Commands.hs | 31 ++++++--- src/Concordium/Client/Runner.hs | 111 +++++++++++++----------------- 2 files changed, 67 insertions(+), 75 deletions(-) diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 7c2efb91..2e19e8c7 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -182,16 +182,17 @@ registerDataParser = data TransactionCmd = TransactionSignAndSubmit - { tsFile :: !FilePath, - tsInteractionOpts :: !InteractionOpts + { tssFile :: !FilePath, + tssInteractionOpts :: !InteractionOpts } | TransactionSubmit { tsFile :: !FilePath, tsInteractionOpts :: !InteractionOpts } | TransactionAddSignature - { tsFile :: !FilePath, - signers :: !(Maybe Text) + { tasFile :: !FilePath, + tasSigners :: !(Maybe Text), + tasToKeys :: !(Maybe FilePath) } | TransactionStatus { tsHash :: !Text, @@ -666,6 +667,7 @@ interactionOptsParser = <$> (not <$> switch (long "no-confirm" <> help "Do not ask for confirmation before proceeding to send the transaction.")) <*> (not <$> switch (long "no-submit" <> help "Do not submit transaction on-chain. Write signed transaction to file instead.")) <*> (not <$> switch (long "no-wait" <> help "Exit right after sending the transaction without waiting for it to be committed and finalized.")) + programOptions :: Parser Options programOptions = Options @@ -754,9 +756,18 @@ transactionAddSignatureCmd = ( info ( TransactionAddSignature <$> strArgument (metavar "FILE" <> help "File containing a signed transaction in JSON format.") - <*> optional (strOption (long "signers" <> metavar "SIGNERS" <> help "Specification of which (local) keys to sign with. Example: \"0:1,0:2,3:0,3:1\" specifies that credential holder 0 signs with keys 1 and 2, while credential holder 3 signs with keys 0 and 1")) + <*> optional + (strOption (long "signers" <> metavar "SIGNERS" <> help "Specification of which (local) keys to sign with. Example: \"0:1,0:2,3:0,3:1\" specifies that credential holder 0 signs with keys 1 and 2, while credential holder 3 signs with keys 0 and 1")) + <*> optional + (strOption (long "keys" <> metavar "KEYS" <> help "Any number of sign/verify keys specified in a JSON file.")) + ) + ( progDescDoc $ + docFromLines $ + [ "Adds a signature to the transaction in the file.", + "Expected format of the `KEYS` file:" + ] + ++ expectedKeysFileFormat ) - (progDesc "Adds a signature to the transaction in the file.") ) transactionDeployCredentialCmd :: Mod CommandFields TransactionCmd @@ -1466,7 +1477,7 @@ configAccountAddKeysCmd = [ "Add one or several key pairs to a specific account configuration.", "Expected format of the key file:" ] - ++ expectedAddOrUpdateKeysFileFormat + ++ expectedKeysFileFormat ) ) @@ -1484,12 +1495,12 @@ configAccountUpdateKeysCmd = [ "Update one or several key pairs to a specific account configuration.", "Expected format of the key file:" ] - ++ expectedAddOrUpdateKeysFileFormat + ++ expectedKeysFileFormat ) ) -expectedAddOrUpdateKeysFileFormat :: [String] -expectedAddOrUpdateKeysFileFormat = +expectedKeysFileFormat :: [String] +expectedKeysFileFormat = [ " {", " \"cidx\": {", " \"kidx\": {", diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index b20ff9e7..b42b403c 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -657,30 +657,30 @@ readAndDisplayAccountTransaction fname backend = do -- Decode JSON file content into an AccountTransaction accountTransaction <- case decode fileContent of - Just tx -> do - -- Display the transaction from the JSON file - logInfo ["Transaction in file: "] - logInfo [[i| #{showPrettyJSON tx}.|]] + Just tx -> do + -- Display the transaction from the JSON file + logInfo ["Transaction in file: "] + logInfo [[i| #{showPrettyJSON tx}.|]] - return tx - Nothing -> logFatal ["Failed to decode file content into AccountTransaction type"] + return tx + Nothing -> logFatal ["Failed to decode file content into AccountTransaction type"] -- Get current protocol version pv <- withClient backend $ do - cs <- getResponseValueOrDie =<< getConsensusInfo - return $ Queries.csProtocolVersion cs + cs <- getResponseValueOrDie =<< getConsensusInfo + return $ Queries.csProtocolVersion cs -- Decode the display the payload from the transaction let encPayload = Types.atrPayload accountTransaction let decPayload = case Types.promoteProtocolVersion pv of - Types.SomeProtocolVersion spv -> Types.decodePayload spv (Types.payloadSize encPayload) encPayload + Types.SomeProtocolVersion spv -> Types.decodePayload spv (Types.payloadSize encPayload) encPayload case decPayload of - Right payload -> do - logInfo [[i| Decoded payload:|]] - logInfo [[i| #{showPrettyJSON payload}.|]] - Left err -> logFatal ["Decoding of payload failed: " <> err] + Right payload -> do + logInfo [[i| Decoded payload:|]] + logInfo [[i| #{showPrettyJSON payload}.|]] + Left err -> logFatal ["Decoding of payload failed: " <> err] -- TODO: to further decode the `message` in contract update or init transactions, we need a schema. @@ -691,6 +691,7 @@ processTransactionCmd :: TransactionCmd -> Maybe FilePath -> Verbose -> Backend processTransactionCmd action baseCfgDir verbose backend = case action of TransactionSignAndSubmit fname intOpts -> do + -- TODO: the no-submit should be disabled for this command -- TODO Ensure that the "nonce" field is optional in the payload. source <- handleReadFile BSL.readFile fname @@ -708,8 +709,9 @@ processTransactionCmd action baseCfgDir verbose backend = tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] TransactionSubmit fname intOpts -> do + -- TODO: the no-submit should be disabled for this command accountTransaction <- readAndDisplayAccountTransaction fname backend - + when (ioConfirm intOpts) $ do confirmed <- askConfirmation $ Just "Do you want to send the transaction on chain? " unless confirmed exitTransactionCancelled @@ -733,7 +735,7 @@ processTransactionCmd action baseCfgDir verbose backend = when (ioTail intOpts) $ do tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] - TransactionAddSignature fname signers -> do + TransactionAddSignature fname signers toKeys -> do accountTransaction <- readAndDisplayAccountTransaction fname backend -- Get encoded payload let encPayload = Types.atrPayload accountTransaction @@ -741,13 +743,20 @@ processTransactionCmd action baseCfgDir verbose backend = -- Extract accountKeyMap to be used to sign the transaction baseCfg <- getBaseConfig baseCfgDir verbose let header = Types.atrHeader accountTransaction - let signerAccountAddressText = Text.pack $ show (Types.thSender header) + let signerAccountText = Text.pack $ show (Types.thSender header) -- TODO: should we check if the `nonce` still makes sense as read from the file vs the one on-chain. -- (and throw error if additional txs have been sent by account meanwhile meaning the account nonce is not valid anymore). - -- TODO: better name than `getAccountCfgFromTxOpts2` and consolidate both functions - encryptedSigningData <- getAccountCfgFromTxOpts2 baseCfg signerAccountAddressText signers + keysArg <- case toKeys of + Nothing -> do + logInfo [[i|The local keys associated to account `#{signerAccountText}` will be used for signing:|]] + return Nothing + Just filePath -> do + logInfo [[i|Using keys from file for signing:|]] + AE.eitherDecodeFileStrict filePath `withLogFatalIO` ("cannot decode keys: " ++) + + encryptedSigningData <- getAccountCfg baseCfg signers (Just signerAccountText) keysArg accountKeyMap <- liftIO $ failOnError $ decryptAccountKeyMapInteractive (esdKeys encryptedSigningData) Nothing Nothing -- Sign transaction and extract the signature map B (new signatures to be added) @@ -1104,58 +1113,30 @@ warnSuspiciousExpiry expiryArg now logWarn ["expiration time is in more than one hour"] | otherwise = return () --- | Get accountCfg from the config folder and return EncryptedSigningData or logFatal if the keys are not provided in txOpts. -getAccountCfgFromTxOpts2 :: BaseConfig -> Text -> Maybe Text -> IO EncryptedSigningData -getAccountCfgFromTxOpts2 baseCfg signerAccountAddressText signers = do - let chosenKeysMaybe :: Maybe (Map.Map ID.CredentialIndex [ID.KeyIndex]) = case signers of - Nothing -> Nothing - Just t -> - Just $ - let insertKey c k acc = case Map.lookup c acc of - Nothing -> Map.insert c [k] acc - Just x -> Map.insert c ([k] ++ x) acc - in foldl' (\acc (c, k) -> insertKey c k acc) Map.empty $ fmap ((\(p1, p2) -> (read . Text.unpack $ p1, read . Text.unpack $ Text.drop 1 p2)) . Text.breakOn ":") $ Text.split (== ',') t - - (_, accCfg) <- getAccountConfig (Just signerAccountAddressText) baseCfg Nothing Nothing Nothing AssumeInitialized - - let keys = acKeys accCfg - - case chosenKeysMaybe of - Nothing -> return EncryptedSigningData{esdKeys = keys, esdAddress = acAddr accCfg, esdEncryptionKey = acEncryptionKey accCfg} - Just chosenKeys -> do - let newKeys = Map.intersection keys chosenKeys - let filteredKeys = - Map.mapWithKey - ( \c m -> - Map.filterWithKey - ( \k _ -> case Map.lookup c chosenKeys of - Nothing -> False - Just keyList -> elem k keyList - ) - m - ) - newKeys - _ <- - Map.traverseWithKey - ( \c keyIndices -> case Map.lookup c newKeys of - Nothing -> logFatal ["No credential holder with index " ++ (show c) ++ "."] - Just credHolderMap -> do - let warnIfMissingKey keyIndex = case Map.lookup keyIndex credHolderMap of - Nothing -> logFatal ["No key with index " ++ (show keyIndex) ++ " for credential holder " ++ (show c) ++ "."] - Just _ -> return () -- Key found, do nothing. - -- We could add the key to a map in this case, replacing the intersection and mapWithKey steps above. - mapM_ warnIfMissingKey keyIndices - ) - chosenKeys - return EncryptedSigningData{esdKeys = filteredKeys, esdAddress = acAddr accCfg, esdEncryptionKey = acEncryptionKey accCfg} - --- | Get accountCfg from the config folder and return EncryptedSigningData or logFatal if the keys are not provided in txOpts. +-- | Extract the account configuration and return the `EncryptedSigningData` (the encrypted keys/data to be used for e.g. singing a transaction). +-- If provided via a flag to a key file (in `toKeys txOpts`), the explicit keys provided will take precedence over the local keys stored, +-- otherwise this function attempts to lookup local keys from the key directory. +-- If some keys (e.g. '0:0,0:1,1:2') are specified (in `toSigners txOpts`), only the specified keys from the account will be looked up locally, +-- otherwise all locally stored keys from the account will be returned. +-- The function throws an error: +-- - if a key file is provided via a flag but the keys cannot be read from the file. +-- - if NO key file is provided via a flag and the lookup of local keys from the key directory fails. getAccountCfgFromTxOpts :: BaseConfig -> TransactionOpts energyOrMaybe -> IO EncryptedSigningData getAccountCfgFromTxOpts baseCfg txOpts = do keysArg <- case toKeys txOpts of Nothing -> return Nothing Just filePath -> AE.eitherDecodeFileStrict filePath `withLogFatalIO` ("cannot decode keys: " ++) let chosenKeysText = toSigners txOpts + let signerAccountText = toSender txOpts + getAccountCfg baseCfg chosenKeysText signerAccountText keysArg + +-- | Extract the account configuration and return the EncryptedSigningData (the encrypted keys/data to be used for e.g. singing a transaction). +-- If the `keysArg` map is provided, the keys in the map will take precedence over the local keys stored, +-- otherwise this function attempts to lookup local keys from the key directory. +-- The function throws an error: +-- - if `keysArg` is NOT provided and the lookup of local keys from the key directory fails. +getAccountCfg :: BaseConfig -> Maybe Text -> Maybe Text -> Maybe (Map.Map ID.CredentialIndex (Map.Map ID.KeyIndex EncryptedAccountKeyPair)) -> IO EncryptedSigningData +getAccountCfg baseCfg chosenKeysText signerAccountText keysArg = do let chosenKeysMaybe :: Maybe (Map.Map ID.CredentialIndex [ID.KeyIndex]) = case chosenKeysText of Nothing -> Nothing Just t -> @@ -1164,7 +1145,7 @@ getAccountCfgFromTxOpts baseCfg txOpts = do Nothing -> Map.insert c [k] acc Just x -> Map.insert c ([k] ++ x) acc in foldl' (\acc (c, k) -> insertKey c k acc) Map.empty $ fmap ((\(p1, p2) -> (read . Text.unpack $ p1, read . Text.unpack $ Text.drop 1 p2)) . Text.breakOn ":") $ Text.split (== ',') t - accCfg <- snd <$> getAccountConfig (toSender txOpts) baseCfg Nothing keysArg Nothing AssumeInitialized + accCfg <- snd <$> getAccountConfig signerAccountText baseCfg Nothing keysArg Nothing AssumeInitialized let keys = acKeys accCfg case chosenKeysMaybe of Nothing -> return EncryptedSigningData{esdKeys = keys, esdAddress = acAddr accCfg, esdEncryptionKey = acEncryptionKey accCfg} From a1bb39e428f7cce70aca7c8b335a8781a033a446 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 10 May 2024 11:19:44 +0300 Subject: [PATCH 10/22] Change to use type for a partially signed transaction --- deps/concordium-base | 2 +- src/Concordium/Client/Commands.hs | 44 ++++++++- src/Concordium/Client/Runner.hs | 145 +++++++++++++++++------------- 3 files changed, 124 insertions(+), 67 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index e9e71c16..fec8b61c 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit e9e71c168600edecabe02be0974719665a7cd55a +Subproject commit fec8b61cec08ecb32fdc39e91b6ad0757ab34f72 diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 2e19e8c7..512d6a96 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -746,7 +746,13 @@ transactionSubmitCmd = <$> strArgument (metavar "FILE" <> help "File containing a signed transaction in JSON format.") <*> interactionOptsParser ) - (progDesc "Parse signed transaction and send it to the node.") + ( progDescDoc $ + docFromLines $ + [ "Parse signed transaction and send it to the node.", + "Expected format of the signed transaction in the `FILE`:" + ] + ++ expectedSignedTransactionFormat + ) ) transactionAddSignatureCmd :: Mod CommandFields TransactionCmd @@ -757,19 +763,51 @@ transactionAddSignatureCmd = ( TransactionAddSignature <$> strArgument (metavar "FILE" <> help "File containing a signed transaction in JSON format.") <*> optional - (strOption (long "signers" <> metavar "SIGNERS" <> help "Specification of which (local) keys to sign with. Example: \"0:1,0:2,3:0,3:1\" specifies that credential holder 0 signs with keys 1 and 2, while credential holder 3 signs with keys 0 and 1")) + ( strOption + ( long "signers" <> metavar "SIGNERS" <> help "Specification of which (local) keys to sign with. Example: \"0:1,0:2,3:0,3:1\" specifies that credential holder 0 signs with keys 1 and 2, while credential holder 3 signs with keys 0 and 1" + ) + ) <*> optional (strOption (long "keys" <> metavar "KEYS" <> help "Any number of sign/verify keys specified in a JSON file.")) ) ( progDescDoc $ docFromLines $ [ "Adds a signature to the transaction in the file.", - "Expected format of the `KEYS` file:" + "Expected format of the signed transaction in the `FILE`:" ] + ++ expectedSignedTransactionFormat + ++ [ "Expected format of the keys in the `KEYS` file:" + ] ++ expectedKeysFileFormat ) ) +expectedSignedTransactionFormat :: [String] +expectedSignedTransactionFormat = + [ " {", + " \"energy\": 5000,", + " \"expiry\": 1715188938,", + " \"nonce\": 12,", + " \"payload\": {", + " \"address\": {", + " \"index\": 3383,", + " \"subindex\": 0", + " },", + " \"amount\": \"0\",", + " \"message\": \"01000101420c0000000000000000000000000000\",", + " \"receiveName\": \"cis2-bridgeable.updateOperator\"", + " },", + " \"sender\": \"4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix\",", + " \"signature\": {", + " \"0\": {", + " \"0\": \"6f17c110965054b262ef0d6dee02f77dccb7bd031c2af324b544f5ee3e6e18b3fd1be8a95782e92a89dd40a1b69cad8a37e8b86fc9107c8528d8267212cf030b\"", + " }", + " },", + " \"transactionType\": \"update\",", + " \"version\": 1", + " }" + ] + transactionDeployCredentialCmd :: Mod CommandFields TransactionCmd transactionDeployCredentialCmd = command diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index b42b403c..8e507e68 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -649,42 +649,71 @@ getTxContractInfoWithSchemas schemaFile status = do MultipleBlocksUnambiguous bhs ts -> map (,f ts) bhs MultipleBlocksAmbiguous bhts -> map (second f) bhts --- | Read and display an `AccountTransaction` from a JSON file. --- Returns the `AccountTransaction`. -readAndDisplayAccountTransaction :: FilePath -> Backend -> IO Types.AccountTransaction -readAndDisplayAccountTransaction fname backend = do - fileContent <- liftIO $ BSL8.readFile fname - - -- Decode JSON file content into an AccountTransaction - accountTransaction <- case decode fileContent of - Just tx -> do - -- Display the transaction from the JSON file - logInfo ["Transaction in file: "] - logInfo [[i| #{showPrettyJSON tx}.|]] +-- | Convert an `AccountTransaction` into a `SignedTransaction` and write it to a JSON file. +writeSignedTransactionToFile :: Types.AccountTransaction -> FilePath -> Bool -> OverwriteSetting -> IO () +writeSignedTransactionToFile tx outFile verbose overwriteSetting = do + let header = Types.atrHeader tx - return tx - Nothing -> logFatal ["Failed to decode file content into AccountTransaction type"] + -- TODO Get current protocol version + -- pv <- withClient backend $ do + -- cs <- getResponseValueOrDie =<< getConsensusInfo + -- return $ Queries.csProtocolVersion cs - -- Get current protocol version - pv <- withClient backend $ do - cs <- getResponseValueOrDie =<< getConsensusInfo - return $ Queries.csProtocolVersion cs + -- Decode the payload from the transaction + let encPayload = Types.atrPayload tx - -- Decode the display the payload from the transaction - let encPayload = Types.atrPayload accountTransaction - - let decPayload = case Types.promoteProtocolVersion pv of + let decPayload = case Types.promoteProtocolVersion Types.P6 of Types.SomeProtocolVersion spv -> Types.decodePayload spv (Types.payloadSize encPayload) encPayload - case decPayload of - Right payload -> do - logInfo [[i| Decoded payload:|]] - logInfo [[i| #{showPrettyJSON payload}.|]] + jsonPayload <- case decPayload of + Right payload -> return payload Left err -> logFatal ["Decoding of payload failed: " <> err] + -- Note: to further decode the `message` in contract update or init transactions, we need a schema. + + let signedTransaction = + Types.SignedTransaction + Types.signedTransactionVersion + (Types.getTransactionType jsonPayload) + (Types.thEnergyAmount header) + (Types.thExpiry header) + (Types.thNonce header) + (Types.thSender header) + jsonPayload + (Types.atrSignature tx) + + let txJson = AE.encodePretty signedTransaction + success <- liftIO $ handleWriteFile BSL.writeFile overwriteSetting verbose outFile txJson + + if success + then logSuccess [[i|Wrote transaction successfully to the file '#{outFile}'|]] + else logError [[i|Failed to write transaction to the file '#{outFile}'|]] + +-- | Read and display a `SignedTransaction` from a JSON file. +-- Returns the associated `AccountTransaction`. +readSignedTransactionFromFile :: FilePath -> IO Types.AccountTransaction +readSignedTransactionFromFile fname = do + fileContent <- liftIO $ BSL8.readFile fname + + -- Decode JSON file content into an `SignedTransaction` type. + + let parsedSignedTransaction :: Either String Types.SignedTransaction + parsedSignedTransaction = eitherDecode fileContent + signedTransaction <- case parsedSignedTransaction of + Right tx -> do + logInfo ["Transaction in file: "] + logInfo [[i| #{showPrettyJSON tx}.|]] + return tx + Left parseError -> logFatal [[i| Failed to decode file content into signedTransaction type: #{parseError}.|]] + + -- Create the associated `AccountTransaction` type. + let encPayload = Types.encodePayload $ Types.stPayload signedTransaction + let header = Types.TransactionHeader (Types.stSigner signedTransaction) (Types.stNonce signedTransaction) (Types.stEnergy signedTransaction) (Types.payloadSize encPayload) (Types.stExpiryTime signedTransaction) + let signHash = Types.transactionSignHashFromHeaderPayload header encPayload + -- TODO: to further decode the `message` in contract update or init transactions, we need a schema. - return accountTransaction + return $ Types.AccountTransaction (Types.stSignature signedTransaction) header encPayload signHash -- | Process a 'transaction ...' command. processTransactionCmd :: TransactionCmd -> Maybe FilePath -> Verbose -> Backend -> IO () @@ -710,7 +739,7 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess ["transaction successfully completed"] TransactionSubmit fname intOpts -> do -- TODO: the no-submit should be disabled for this command - accountTransaction <- readAndDisplayAccountTransaction fname backend + accountTransaction <- readSignedTransactionFromFile fname when (ioConfirm intOpts) $ do confirmed <- askConfirmation $ Just "Do you want to send the transaction on chain? " @@ -736,7 +765,7 @@ processTransactionCmd action baseCfgDir verbose backend = tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] TransactionAddSignature fname signers toKeys -> do - accountTransaction <- readAndDisplayAccountTransaction fname backend + accountTransaction <- readSignedTransactionFromFile fname -- Get encoded payload let encPayload = Types.atrPayload accountTransaction @@ -770,14 +799,10 @@ processTransactionCmd action baseCfgDir verbose backend = let unionSignaturesMap = Map.unionWith Map.union sigAMap sigBMap -- Create final signed transaction including signtures A and B - let finalTransaction = AE.encodePretty accountTransaction{Types.atrSignature = Types.TransactionSignature unionSignaturesMap} - - -- TODO: remove the extra confirmation - -- Write finalTransaction to file - success <- liftIO $ handleWriteFile BSL.writeFile PromptBeforeOverwrite verbose fname finalTransaction - if success - then liftIO $ logSuccess [[i|Added signature successfully to the transaction in the file '#{fname}'|]] - else liftIO $ logError [[i|Failed to write signature to the file '#{fname}'|]] + let finalTransaction = accountTransaction{Types.atrSignature = Types.TransactionSignature unionSignaturesMap} + + -- Write final signed transaction to file + liftIO $ writeSignedTransactionToFile finalTransaction fname verbose AllowOverwrite TransactionDeployCredential fname intOpts -> do source <- handleReadFile BSL.readFile fname withClient backend $ do @@ -1600,7 +1625,7 @@ startTransaction :: -- | The decrypted account signing keys. If not provided, the encrypted keys -- from the 'TransactionConfig' will be used, and for each the password will be queried. Maybe AccountKeyMap -> - ClientMonad m Types.BareBlockItem + ClientMonad m Types.AccountTransaction startTransaction txCfg pl confirmNonce maybeAccKeys = do let TransactionConfig { tcEnergy = energy, @@ -1681,7 +1706,8 @@ signAndProcessTransaction verbose txCfg pl intOpts = do logInfo [[i| Send signed transaction to node.|]] -- Send transaction on chain - sbiRes <- sendBlockItem tx + let bareBlockItem = Types.NormalTransaction tx + sbiRes <- sendBlockItem bareBlockItem let res = case sbiRes of StatusOk resp -> Right resp StatusNotOk (status, err) -> Left [i|GRPC response with status '#{status}': #{err}|] @@ -1690,24 +1716,19 @@ signAndProcessTransaction verbose txCfg pl intOpts = do case res of Left err -> logFatal ["Transaction not accepted by the node: " <> err] Right _ -> do - let hash = getBlockItemHash tx + let hash = getBlockItemHash bareBlockItem logSuccess [printf "transaction '%s' sent to the node" (show hash)] if ioTail intOpts then Just <$> tailTransaction verbose hash else return Nothing else do - logInfo [[i| Write signed transaction to file. Don't send it to the node.|]] + logInfo [[i| Write signed transaction to file. Will not send it to the node.|]] -- TODO: pass in output file via flag. let outFile = "./transaction.json" - let txJson = AE.encodePretty tx - success <- liftIO $ handleWriteFile BSL.writeFile PromptBeforeOverwrite verbose outFile txJson - - if success - then liftIO $ logSuccess [[i|Wrote transaction successfully to the file '#{outFile}'|]] - else liftIO $ logError [[i|Failed to write transaction to the file '#{outFile}'|]] - + -- Write signed transaction to file + liftIO $ writeSignedTransactionToFile tx outFile verbose PromptBeforeOverwrite return Nothing -- | Continuously query and display transaction status until the transaction is finalized. @@ -4555,13 +4576,14 @@ processTransaction_ transaction _verbose = do Just nonce -> return nonce txPayload <- convertTransactionJsonPayload $ payload transaction return $ - encodeAndSignTransaction - txPayload - sender - (thEnergyAmount header) - nonce - (thExpiry header) - accountKeys + Types.NormalTransaction $ + encodeAndSignTransaction + txPayload + sender + (thEnergyAmount header) + nonce + (thExpiry header) + accountKeys sbiRes <- sendBlockItem tx let res = case sbiRes of StatusOk resp -> Right resp @@ -4617,8 +4639,7 @@ convertTransactionJsonPayload = \case CT.TransferToEncrypted{..} -> return $ Types.TransferToEncrypted{..} CT.EncryptedAmountTransfer{..} -> return Types.EncryptedAmountTransfer{..} --- | Sign a transaction payload and configuration into a "normal" transaction, --- which is ready to be sent. +-- | Sign a transaction payload and configuration into a "normal" AccountTransaction. encodeAndSignTransaction :: Types.Payload -> Types.AccountAddress -> @@ -4626,11 +4647,10 @@ encodeAndSignTransaction :: Types.Nonce -> Types.TransactionExpiryTime -> AccountKeyMap -> - Types.BareBlockItem + Types.AccountTransaction encodeAndSignTransaction txPayload = formatAndSignTransaction (Types.encodePayload txPayload) --- | Format the header of the transaction and sign it together with the encoded transaction payload and return a "normal" transaction, --- which is ready to be sent. +-- | Format the header of the transaction and sign it together with the encoded transaction payload and return a "normal" AccountTransaction. formatAndSignTransaction :: Types.EncodedPayload -> Types.AccountAddress -> @@ -4638,9 +4658,8 @@ formatAndSignTransaction :: Types.Nonce -> Types.TransactionExpiryTime -> AccountKeyMap -> - Types.BareBlockItem -formatAndSignTransaction encPayload sender energy nonce expiry accKeys = - Types.NormalTransaction $ signEncodedTransaction encPayload header accKeys + Types.AccountTransaction +formatAndSignTransaction encPayload sender energy nonce expiry = signEncodedTransaction encPayload header where header = Types.TransactionHeader From bcaad6f9ec9e5b6f95c59a0f973ecb615f78e0f3 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 10 May 2024 20:34:45 +0300 Subject: [PATCH 11/22] Extend toJSON and fromJSON to all payload types --- deps/concordium-base | 2 +- src/Concordium/Client/Runner.hs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index fec8b61c..e690cb5b 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit fec8b61cec08ecb32fdc39e91b6ad0757ab34f72 +Subproject commit e690cb5b53216fa0b2024ba03cbe2d90d98d5f37 diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 8e507e68..7e616833 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -674,7 +674,6 @@ writeSignedTransactionToFile tx outFile verbose overwriteSetting = do let signedTransaction = Types.SignedTransaction Types.signedTransactionVersion - (Types.getTransactionType jsonPayload) (Types.thEnergyAmount header) (Types.thExpiry header) (Types.thNonce header) From c200ed9dd8333ec7b50f0243b074ddf37d3a986e Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 13 May 2024 18:11:36 +0300 Subject: [PATCH 12/22] Add output file path --- deps/concordium-base | 2 +- src/Concordium/Client/Commands.hs | 18 +++++-- src/Concordium/Client/Runner.hs | 81 ++++++++++++++++++------------- 3 files changed, 62 insertions(+), 39 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index e690cb5b..7baf5a3e 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit e690cb5b53216fa0b2024ba03cbe2d90d98d5f37 +Subproject commit 7baf5a3ec97d49e63af7b44734799779dbce7340 diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 512d6a96..0f2cf5ef 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -255,7 +255,9 @@ data AccountCmd AccountEncrypt { aeTransactionOpts :: !(TransactionOpts (Maybe Energy)), -- | Amount to transfer from public to encrypted balance. - aeAmount :: !Amount + aeAmount :: !Amount, + -- | Optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain. + aeOutFile :: !(Maybe FilePath) } | -- | Transfer part of the encrypted balance to the public balance of the -- account. @@ -265,13 +267,16 @@ data AccountCmd adAmount :: !Amount, -- | Which indices of incoming amounts to use as inputs. -- If none are provided all existing ones will be used. - adIndex :: !(Maybe Int) + adIndex :: !(Maybe Int), + -- | Optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain. + adOutFile :: !(Maybe FilePath) } | -- | Updated credentials and account threshold (i.e. how many credential holders that need to sign transactions) AccountUpdateCredentials { aucNewCredInfos :: !(Maybe FilePath), -- File containing the new CredentialDeploymentInformation's aucRemoveCredIds :: !(Maybe FilePath), -- File containing the CredentialRegistrationID's for the credentials to be removed aucNewThreshold :: !AccountThreshold, -- The new account threshold + aucOutFile :: !(Maybe FilePath), -- Otional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain. aucTransactionOpts :: !(TransactionOpts (Maybe Energy)) } | -- | Show an alias for the account. @@ -435,13 +440,13 @@ data TransactionOpts energyOrMaybe = TransactionOpts toNonce :: !(Maybe Nonce), toMaxEnergyAmount :: !energyOrMaybe, toExpiration :: !(Maybe Text), + toOutFile :: !(Maybe FilePath), -- Optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain. toInteractionOpts :: !InteractionOpts } deriving (Show) data InteractionOpts = InteractionOpts { ioConfirm :: !Bool, - ioSubmit :: !Bool, ioTail :: !Bool } deriving (Show) @@ -659,13 +664,13 @@ transactionOptsParserBuilder energyOrMaybeParser = <*> optional (option auto (long "nonce" <> metavar "NONCE" <> help "Transaction nonce.")) <*> energyOrMaybeParser <*> optional (strOption (long "expiry" <> metavar "EXPIRY" <> help "Expiration time of a transaction, specified as a relative duration (\"30s\", \"5m\", etc.) or UNIX epoch timestamp.")) + <*> optional (strOption (long "outFile" <> metavar "FILE" <> help "An optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) <*> interactionOptsParser interactionOptsParser :: Parser InteractionOpts interactionOptsParser = InteractionOpts <$> (not <$> switch (long "no-confirm" <> help "Do not ask for confirmation before proceeding to send the transaction.")) - <*> (not <$> switch (long "no-submit" <> help "Do not submit transaction on-chain. Write signed transaction to file instead.")) <*> (not <$> switch (long "no-wait" <> help "Exit right after sending the transaction without waiting for it to be committed and finalized.")) programOptions :: Parser Options @@ -796,6 +801,7 @@ expectedSignedTransactionFormat = " \"amount\": \"0\",", " \"message\": \"01000101420c0000000000000000000000000000\",", " \"receiveName\": \"cis2-bridgeable.updateOperator\"", + " \"transactionType\": \"update\"", " },", " \"sender\": \"4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix\",", " \"signature\": {", @@ -803,7 +809,6 @@ expectedSignedTransactionFormat = " \"0\": \"6f17c110965054b262ef0d6dee02f77dccb7bd031c2af324b544f5ee3e6e18b3fd1be8a95782e92a89dd40a1b69cad8a37e8b86fc9107c8528d8267212cf030b\"", " }", " },", - " \"transactionType\": \"update\",", " \"version\": 1", " }" ] @@ -997,6 +1002,7 @@ accountEncryptCmd = ( AccountEncrypt <$> transactionOptsParser <*> option (eitherReader amountFromStringInform) (long "amount" <> metavar "CCD-AMOUNT" <> help "The amount to transfer to shielded balance.") + <*> optional (strOption (long "outFile" <> metavar "FILE" <> help "An optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) ) (progDesc "Transfer an amount from public to shielded balance of the account.") ) @@ -1010,6 +1016,7 @@ accountDecryptCmd = <$> transactionOptsParser <*> option (maybeReader amountFromString) (long "amount" <> metavar "CCD-AMOUNT" <> help "The amount to transfer to public balance.") <*> optional (option auto (long "index" <> metavar "INDEX" <> help "Optionally specify the index up to which shielded amounts should be combined.")) + <*> optional (strOption (long "outFile" <> metavar "FILE" <> help "An optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) ) (progDesc "Transfer an amount from shielded to public balance of the account.") ) @@ -1051,6 +1058,7 @@ accountUpdateCredentialsCmd = <$> optional (strOption (long "new-credentials" <> metavar "FILE" <> help "File containing the new credential deployment informations.")) <*> optional (strOption (long "remove-credentials" <> metavar "FILE" <> help "File containing credential registration ids of the credentials to be removed.")) <*> option auto (long "new-threshold" <> metavar "THRESHOLD" <> help "New account threshold, i.e. how many credential holders needed to sign a transaction.") + <*> optional (strOption (long "outFile" <> metavar "FILE" <> help "An optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) <*> transactionOptsParser ) ( progDescDoc $ diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 7e616833..1b9f5ad1 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -719,7 +719,6 @@ processTransactionCmd :: TransactionCmd -> Maybe FilePath -> Verbose -> Backend processTransactionCmd action baseCfgDir verbose backend = case action of TransactionSignAndSubmit fname intOpts -> do - -- TODO: the no-submit should be disabled for this command -- TODO Ensure that the "nonce" field is optional in the payload. source <- handleReadFile BSL.readFile fname @@ -737,7 +736,6 @@ processTransactionCmd action baseCfgDir verbose backend = tailTransaction_ verbose hash logSuccess ["transaction successfully completed"] TransactionSubmit fname intOpts -> do - -- TODO: the no-submit should be disabled for this command accountTransaction <- readSignedTransactionFromFile fname when (ioConfirm intOpts) $ do @@ -854,8 +852,9 @@ processTransactionCmd action baseCfgDir verbose backend = putStrLn "" let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts liftIO $ transferTransactionConfirm ttxCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts outFile TransactionSendWithSchedule receiver schedule maybeMemo txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -906,8 +905,9 @@ processTransactionCmd action baseCfgDir verbose backend = logWarn ["Transaction Cancelled"] else do let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts liftIO $ transferWithScheduleTransactionConfirm ttxCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts outFile TransactionEncryptedTransfer txOpts receiver amount index maybeMemo -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -951,8 +951,9 @@ processTransactionCmd action baseCfgDir verbose backend = putStrLn "" let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts encryptedTransferTransactionConfirm ettCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg payload intOpts + signAndProcessTransaction_ verbose txCfg payload intOpts outFile TransactionRegisterData file txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose rdCfg <- getRegisterDataTransactionCfg baseCfg txOpts file @@ -968,10 +969,11 @@ processTransactionCmd action baseCfgDir verbose backend = logInfo ["Registering data..."] let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts let pl = registerDataTransactionPayload rdCfg withClient backend $ do - mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile let extractDataRegistered = extractFromTsr $ \case Types.DataRegistered rd -> Just rd _ -> Nothing @@ -1679,8 +1681,10 @@ signAndProcessTransaction_ :: Types.EncodedPayload -> -- | How interactive should sending and tailing be InteractionOpts -> + -- | An optional file name to output the signed/partially-signed transaction to instead of sending it to the node + Maybe FilePath -> ClientMonad m () -signAndProcessTransaction_ verbose txCfg pl intOpts = void $ signAndProcessTransaction verbose txCfg pl intOpts +signAndProcessTransaction_ verbose txCfg pl intOpts outFile = void $ signAndProcessTransaction verbose txCfg pl intOpts outFile -- | Sign a transaction and process transaction by either send it to the node or write it to a file. -- If send to the node, optionally tail it (see 'tailTransaction' below). @@ -1696,12 +1700,20 @@ signAndProcessTransaction :: Types.EncodedPayload -> -- | How interactive should sending and tailing be InteractionOpts -> + -- | An optional file name to output the signed/partially-signed transaction to instead of sending it to the node + Maybe FilePath -> ClientMonad m (Maybe TransactionStatusResult) -signAndProcessTransaction verbose txCfg pl intOpts = do +signAndProcessTransaction verbose txCfg pl intOpts outFile = do tx <- startTransaction txCfg pl (ioConfirm intOpts) Nothing - if ioSubmit intOpts - then do + case outFile of + Just filePath -> do + logInfo [[i| Write signed transaction to file. Will not send it to the node.|]] + + -- Write signed transaction to file + liftIO $ writeSignedTransactionToFile tx filePath verbose PromptBeforeOverwrite + return Nothing + Nothing -> do logInfo [[i| Send signed transaction to node.|]] -- Send transaction on chain @@ -1721,14 +1733,6 @@ signAndProcessTransaction verbose txCfg pl intOpts = do if ioTail intOpts then Just <$> tailTransaction verbose hash else return Nothing - else do - logInfo [[i| Write signed transaction to file. Will not send it to the node.|]] - -- TODO: pass in output file via flag. - let outFile = "./transaction.json" - - -- Write signed transaction to file - liftIO $ writeSignedTransactionToFile tx outFile verbose PromptBeforeOverwrite - return Nothing -- | Continuously query and display transaction status until the transaction is finalized. tailTransaction_ :: (MonadIO m) => Bool -> Types.TransactionHash -> ClientMonad m () @@ -1931,9 +1935,10 @@ processAccountCmd action baseCfgDir verbose backend = putStrLn "" let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts liftIO $ credentialUpdateKeysTransactionConfirm aukCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts - AccountUpdateCredentials cdisFile removeCidsFile newThreshold txOpts -> do + signAndProcessTransaction_ verbose txCfg pl intOpts outFile + AccountUpdateCredentials cdisFile removeCidsFile newThreshold outFile txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -1962,7 +1967,7 @@ processAccountCmd action baseCfgDir verbose backend = auctcNewThreshold = newThreshold } liftIO $ accountUpdateCredentialsTransactionConfirm aucCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg epayload intOpts + signAndProcessTransaction_ verbose txCfg epayload intOpts outFile AccountEncrypt{..} -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -1979,7 +1984,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts aeTransactionOpts accountEncryptTransactionConfirm aetxCfg (ioConfirm intOpts) - withClient backend $ signAndProcessTransaction_ verbose txCfg pl intOpts + withClient backend $ signAndProcessTransaction_ verbose txCfg pl intOpts aeOutFile AccountDecrypt{..} -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -2011,7 +2016,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts adTransactionOpts accountDecryptTransactionConfirm adtxCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts adOutFile AccountShowAlias addrOrName alias -> do baseCfg <- getBaseConfig baseCfgDir verbose case getAccountAddress (bcAccountNameMap baseCfg) addrOrName of @@ -2046,10 +2051,11 @@ processModuleCmd action baseCfgDir verbose backend = logInfo ["deploying module..."] let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts let pl = moduleDeployTransactionPayload mdCfg withClient backend $ do - mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile case extractModRef mTsr of Nothing -> return () Just (Left err) -> logFatal ["module deployment failed:", err] @@ -2299,9 +2305,10 @@ processContractCmd action baseCfgDir verbose backend = unless confirmed exitTransactionCancelled let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts let pl = contractInitTransactionPayload ciCfg withClient backend $ do - mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile case extractContractAddress mTsr of Nothing -> return () Just (Left err) -> logFatal ["contract initialisation failed:", err] @@ -2350,9 +2357,10 @@ processContractCmd action baseCfgDir verbose backend = unless confirmed exitTransactionCancelled let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts let pl = contractUpdateTransactionPayload cuCfg withClient backend $ do - mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile case extractUpdate mTsr of Nothing -> return () Just (Left err) -> logFatal ["updating contract instance failed:", err] @@ -3093,11 +3101,12 @@ processBakerConfigureCmd :: IO () processBakerConfigureCmd baseCfgDir verbose backend txOpts isBakerConfigure cbCapital cbRestakeEarnings cbOpenForDelegation metadataURL cbTransactionFeeCommission cbBakingRewardCommission cbFinalizationRewardCommission inputKeysFile outputKeysFile = do let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts (bakerKeys, txCfg, pl) <- transactionForBakerConfigure (ioConfirm intOpts) withClient backend $ do when isBakerConfigure $ warnAboutMissingAddBakerParameters txCfg mapM_ (warnAboutBadCapital txCfg) cbCapital - result <- signAndProcessTransaction verbose txCfg pl intOpts + result <- signAndProcessTransaction verbose txCfg pl intOpts outFile events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3341,10 +3350,11 @@ processBakerAddCmd :: IO () processBakerAddCmd baseCfgDir verbose backend txOpts abBakingStake abRestakeEarnings inputKeysFile outputKeysFile = do let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts (bakerKeys, txCfg, pl) <- transactionForBakerAdd (ioConfirm intOpts) withClient backend $ do warnAboutBadCapital txCfg abBakingStake - result <- signAndProcessTransaction verbose txCfg pl intOpts + result <- signAndProcessTransaction verbose txCfg pl intOpts outFile events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3502,9 +3512,10 @@ processBakerSetKeysCmd :: IO () processBakerSetKeysCmd baseCfgDir verbose backend txOpts inputKeysFile outputKeysFile = do let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts (bakerKeys, txCfg, pl) <- transactionForBakerSetKeys (ioConfirm intOpts) withClient backend $ do - result <- signAndProcessTransaction verbose txCfg pl intOpts + result <- signAndProcessTransaction verbose txCfg pl intOpts outFile events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3618,10 +3629,11 @@ processBakerRemoveCmd :: IO () processBakerRemoveCmd baseCfgDir verbose backend txOpts = do let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts (txCfg, pl) <- transactionForBakerRemove (ioConfirm intOpts) withClient backend $ do liftIO warnAboutRemoving - signAndProcessTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts outFile where warnAboutRemoving = do cooldownDate <- withClient backend $ do @@ -3667,10 +3679,11 @@ processBakerUpdateStakeBeforeP4Cmd :: IO () processBakerUpdateStakeBeforeP4Cmd baseCfgDir verbose backend txOpts ubsStake = do let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts (txCfg, pl) <- transactionForBakerUpdateStake (ioConfirm intOpts) withClient backend $ do warnAboutBadCapital txCfg ubsStake - signAndProcessTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts outFile where warnAboutBadCapital txCfg capital = do let senderAddr = naAddr . esdAddress . tcEncryptedSigningData $ txCfg @@ -3757,9 +3770,10 @@ processBakerUpdateRestakeCmd :: IO () processBakerUpdateRestakeCmd baseCfgDir verbose backend txOpts ubreRestakeEarnings = do let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts (txCfg, pl) <- transactionForBakerUpdateRestake (ioConfirm intOpts) withClient backend $ do - signAndProcessTransaction_ verbose txCfg pl intOpts + signAndProcessTransaction_ verbose txCfg pl intOpts outFile where transactionForBakerUpdateRestake confirm = do baseCfg <- getBaseConfig baseCfgDir verbose @@ -3948,11 +3962,12 @@ processDelegatorConfigureCmd :: IO () processDelegatorConfigureCmd baseCfgDir verbose backend txOpts cdCapital cdRestakeEarnings cdDelegationTarget = do let intOpts = toInteractionOpts txOpts + let outFile = toOutFile txOpts (txCfg, pl) <- transactionForDelegatorConfigure (ioConfirm intOpts) withClient backend $ do warnInOldProtocol mapM_ (warnAboutBadCapital txCfg) cdCapital - result <- signAndProcessTransaction verbose txCfg pl intOpts + result <- signAndProcessTransaction verbose txCfg pl intOpts outFile warnAboutFailedResult result where warnInOldProtocol = do From d8e02fc9df32feeca0d1dab700b28cc8c7511ab7 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 13 May 2024 19:40:09 +0300 Subject: [PATCH 13/22] Query protocol version from chain --- src/Concordium/Client/Runner.hs | 67 ++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 1b9f5ad1..8d710858 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -650,19 +650,14 @@ getTxContractInfoWithSchemas schemaFile status = do MultipleBlocksAmbiguous bhts -> map (second f) bhts -- | Convert an `AccountTransaction` into a `SignedTransaction` and write it to a JSON file. -writeSignedTransactionToFile :: Types.AccountTransaction -> FilePath -> Bool -> OverwriteSetting -> IO () -writeSignedTransactionToFile tx outFile verbose overwriteSetting = do +writeSignedTransactionToFile :: Types.AccountTransaction -> FilePath -> Bool -> OverwriteSetting -> Types.ProtocolVersion -> IO () +writeSignedTransactionToFile tx outFile verbose overwriteSetting pv = do let header = Types.atrHeader tx - -- TODO Get current protocol version - -- pv <- withClient backend $ do - -- cs <- getResponseValueOrDie =<< getConsensusInfo - -- return $ Queries.csProtocolVersion cs - -- Decode the payload from the transaction let encPayload = Types.atrPayload tx - let decPayload = case Types.promoteProtocolVersion Types.P6 of + let decPayload = case Types.promoteProtocolVersion pv of Types.SomeProtocolVersion spv -> Types.decodePayload spv (Types.payloadSize encPayload) encPayload jsonPayload <- case decPayload of @@ -798,8 +793,13 @@ processTransactionCmd action baseCfgDir verbose backend = -- Create final signed transaction including signtures A and B let finalTransaction = accountTransaction{Types.atrSignature = Types.TransactionSignature unionSignaturesMap} + -- Get protocol version + pv <- liftIO $ withClient backend $ do + cs <- getResponseValueOrDie =<< getConsensusInfo + return $ Queries.csProtocolVersion cs + -- Write final signed transaction to file - liftIO $ writeSignedTransactionToFile finalTransaction fname verbose AllowOverwrite + liftIO $ writeSignedTransactionToFile finalTransaction fname verbose AllowOverwrite pv TransactionDeployCredential fname intOpts -> do source <- handleReadFile BSL.readFile fname withClient backend $ do @@ -854,7 +854,7 @@ processTransactionCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts let outFile = toOutFile txOpts liftIO $ transferTransactionConfirm ttxCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts outFile + signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend TransactionSendWithSchedule receiver schedule maybeMemo txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -907,7 +907,7 @@ processTransactionCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts let outFile = toOutFile txOpts liftIO $ transferWithScheduleTransactionConfirm ttxCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts outFile + signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend TransactionEncryptedTransfer txOpts receiver amount index maybeMemo -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -953,7 +953,7 @@ processTransactionCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts let outFile = toOutFile txOpts encryptedTransferTransactionConfirm ettCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg payload intOpts outFile + signAndProcessTransaction_ verbose txCfg payload intOpts outFile backend TransactionRegisterData file txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose rdCfg <- getRegisterDataTransactionCfg baseCfg txOpts file @@ -973,7 +973,7 @@ processTransactionCmd action baseCfgDir verbose backend = let pl = registerDataTransactionPayload rdCfg withClient backend $ do - mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile backend let extractDataRegistered = extractFromTsr $ \case Types.DataRegistered rd -> Just rd _ -> Nothing @@ -1683,8 +1683,9 @@ signAndProcessTransaction_ :: InteractionOpts -> -- | An optional file name to output the signed/partially-signed transaction to instead of sending it to the node Maybe FilePath -> + Backend -> ClientMonad m () -signAndProcessTransaction_ verbose txCfg pl intOpts outFile = void $ signAndProcessTransaction verbose txCfg pl intOpts outFile +signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend = void $ signAndProcessTransaction verbose txCfg pl intOpts outFile backend -- | Sign a transaction and process transaction by either send it to the node or write it to a file. -- If send to the node, optionally tail it (see 'tailTransaction' below). @@ -1702,16 +1703,22 @@ signAndProcessTransaction :: InteractionOpts -> -- | An optional file name to output the signed/partially-signed transaction to instead of sending it to the node Maybe FilePath -> + Backend -> ClientMonad m (Maybe TransactionStatusResult) -signAndProcessTransaction verbose txCfg pl intOpts outFile = do +signAndProcessTransaction verbose txCfg pl intOpts outFile backend = do tx <- startTransaction txCfg pl (ioConfirm intOpts) Nothing case outFile of Just filePath -> do logInfo [[i| Write signed transaction to file. Will not send it to the node.|]] + -- Get protocol version + pv <- liftIO $ withClient backend $ do + cs <- getResponseValueOrDie =<< getConsensusInfo + return $ Queries.csProtocolVersion cs + -- Write signed transaction to file - liftIO $ writeSignedTransactionToFile tx filePath verbose PromptBeforeOverwrite + liftIO $ writeSignedTransactionToFile tx filePath verbose PromptBeforeOverwrite pv return Nothing Nothing -> do logInfo [[i| Send signed transaction to node.|]] @@ -1937,7 +1944,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts txOpts let outFile = toOutFile txOpts liftIO $ credentialUpdateKeysTransactionConfirm aukCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts outFile + signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend AccountUpdateCredentials cdisFile removeCidsFile newThreshold outFile txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose @@ -1967,7 +1974,7 @@ processAccountCmd action baseCfgDir verbose backend = auctcNewThreshold = newThreshold } liftIO $ accountUpdateCredentialsTransactionConfirm aucCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg epayload intOpts outFile + signAndProcessTransaction_ verbose txCfg epayload intOpts outFile backend AccountEncrypt{..} -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -1984,7 +1991,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts aeTransactionOpts accountEncryptTransactionConfirm aetxCfg (ioConfirm intOpts) - withClient backend $ signAndProcessTransaction_ verbose txCfg pl intOpts aeOutFile + withClient backend $ signAndProcessTransaction_ verbose txCfg pl intOpts aeOutFile backend AccountDecrypt{..} -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -2016,7 +2023,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts adTransactionOpts accountDecryptTransactionConfirm adtxCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts adOutFile + signAndProcessTransaction_ verbose txCfg pl intOpts adOutFile backend AccountShowAlias addrOrName alias -> do baseCfg <- getBaseConfig baseCfgDir verbose case getAccountAddress (bcAccountNameMap baseCfg) addrOrName of @@ -2055,7 +2062,7 @@ processModuleCmd action baseCfgDir verbose backend = let pl = moduleDeployTransactionPayload mdCfg withClient backend $ do - mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile backend case extractModRef mTsr of Nothing -> return () Just (Left err) -> logFatal ["module deployment failed:", err] @@ -2308,7 +2315,7 @@ processContractCmd action baseCfgDir verbose backend = let outFile = toOutFile txOpts let pl = contractInitTransactionPayload ciCfg withClient backend $ do - mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile backend case extractContractAddress mTsr of Nothing -> return () Just (Left err) -> logFatal ["contract initialisation failed:", err] @@ -2360,7 +2367,7 @@ processContractCmd action baseCfgDir verbose backend = let outFile = toOutFile txOpts let pl = contractUpdateTransactionPayload cuCfg withClient backend $ do - mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile + mTsr <- signAndProcessTransaction verbose txCfg (Types.encodePayload pl) intOpts outFile backend case extractUpdate mTsr of Nothing -> return () Just (Left err) -> logFatal ["updating contract instance failed:", err] @@ -3106,7 +3113,7 @@ processBakerConfigureCmd baseCfgDir verbose backend txOpts isBakerConfigure cbCa withClient backend $ do when isBakerConfigure $ warnAboutMissingAddBakerParameters txCfg mapM_ (warnAboutBadCapital txCfg) cbCapital - result <- signAndProcessTransaction verbose txCfg pl intOpts outFile + result <- signAndProcessTransaction verbose txCfg pl intOpts outFile backend events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3354,7 +3361,7 @@ processBakerAddCmd baseCfgDir verbose backend txOpts abBakingStake abRestakeEarn (bakerKeys, txCfg, pl) <- transactionForBakerAdd (ioConfirm intOpts) withClient backend $ do warnAboutBadCapital txCfg abBakingStake - result <- signAndProcessTransaction verbose txCfg pl intOpts outFile + result <- signAndProcessTransaction verbose txCfg pl intOpts outFile backend events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3515,7 +3522,7 @@ processBakerSetKeysCmd baseCfgDir verbose backend txOpts inputKeysFile outputKey let outFile = toOutFile txOpts (bakerKeys, txCfg, pl) <- transactionForBakerSetKeys (ioConfirm intOpts) withClient backend $ do - result <- signAndProcessTransaction verbose txCfg pl intOpts outFile + result <- signAndProcessTransaction verbose txCfg pl intOpts outFile backend events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where @@ -3633,7 +3640,7 @@ processBakerRemoveCmd baseCfgDir verbose backend txOpts = do (txCfg, pl) <- transactionForBakerRemove (ioConfirm intOpts) withClient backend $ do liftIO warnAboutRemoving - signAndProcessTransaction_ verbose txCfg pl intOpts outFile + signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend where warnAboutRemoving = do cooldownDate <- withClient backend $ do @@ -3683,7 +3690,7 @@ processBakerUpdateStakeBeforeP4Cmd baseCfgDir verbose backend txOpts ubsStake = (txCfg, pl) <- transactionForBakerUpdateStake (ioConfirm intOpts) withClient backend $ do warnAboutBadCapital txCfg ubsStake - signAndProcessTransaction_ verbose txCfg pl intOpts outFile + signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend where warnAboutBadCapital txCfg capital = do let senderAddr = naAddr . esdAddress . tcEncryptedSigningData $ txCfg @@ -3773,7 +3780,7 @@ processBakerUpdateRestakeCmd baseCfgDir verbose backend txOpts ubreRestakeEarnin let outFile = toOutFile txOpts (txCfg, pl) <- transactionForBakerUpdateRestake (ioConfirm intOpts) withClient backend $ do - signAndProcessTransaction_ verbose txCfg pl intOpts outFile + signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend where transactionForBakerUpdateRestake confirm = do baseCfg <- getBaseConfig baseCfgDir verbose @@ -3967,7 +3974,7 @@ processDelegatorConfigureCmd baseCfgDir verbose backend txOpts cdCapital cdResta withClient backend $ do warnInOldProtocol mapM_ (warnAboutBadCapital txCfg) cdCapital - result <- signAndProcessTransaction verbose txCfg pl intOpts outFile + result <- signAndProcessTransaction verbose txCfg pl intOpts outFile backend warnAboutFailedResult result where warnInOldProtocol = do From 088ce7d979f764f3423f6536410502d586c02ac3 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 13 May 2024 20:16:17 +0300 Subject: [PATCH 14/22] Simplify reading and writing of signed transaction --- src/Concordium/Client/Runner.hs | 112 +++++++++++++++----------------- transaction.json | 23 +++++++ updateOperator.json | 15 +++++ 3 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 transaction.json create mode 100644 updateOperator.json diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 8d710858..5eaa36db 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -649,33 +649,9 @@ getTxContractInfoWithSchemas schemaFile status = do MultipleBlocksUnambiguous bhs ts -> map (,f ts) bhs MultipleBlocksAmbiguous bhts -> map (second f) bhts --- | Convert an `AccountTransaction` into a `SignedTransaction` and write it to a JSON file. -writeSignedTransactionToFile :: Types.AccountTransaction -> FilePath -> Bool -> OverwriteSetting -> Types.ProtocolVersion -> IO () -writeSignedTransactionToFile tx outFile verbose overwriteSetting pv = do - let header = Types.atrHeader tx - - -- Decode the payload from the transaction - let encPayload = Types.atrPayload tx - - let decPayload = case Types.promoteProtocolVersion pv of - Types.SomeProtocolVersion spv -> Types.decodePayload spv (Types.payloadSize encPayload) encPayload - - jsonPayload <- case decPayload of - Right payload -> return payload - Left err -> logFatal ["Decoding of payload failed: " <> err] - - -- Note: to further decode the `message` in contract update or init transactions, we need a schema. - - let signedTransaction = - Types.SignedTransaction - Types.signedTransactionVersion - (Types.thEnergyAmount header) - (Types.thExpiry header) - (Types.thNonce header) - (Types.thSender header) - jsonPayload - (Types.atrSignature tx) - +-- | Write a `SignedTransaction` to a JSON file. +writeSignedTransactionToFile :: Types.SignedTransaction -> FilePath -> Bool -> OverwriteSetting -> IO () +writeSignedTransactionToFile signedTransaction outFile verbose overwriteSetting = do let txJson = AE.encodePretty signedTransaction success <- liftIO $ handleWriteFile BSL.writeFile overwriteSetting verbose outFile txJson @@ -684,30 +660,23 @@ writeSignedTransactionToFile tx outFile verbose overwriteSetting pv = do else logError [[i|Failed to write transaction to the file '#{outFile}'|]] -- | Read and display a `SignedTransaction` from a JSON file. --- Returns the associated `AccountTransaction`. -readSignedTransactionFromFile :: FilePath -> IO Types.AccountTransaction +-- Returns the associated `SignedTransaction`. +readSignedTransactionFromFile :: FilePath -> IO Types.SignedTransaction readSignedTransactionFromFile fname = do fileContent <- liftIO $ BSL8.readFile fname - -- Decode JSON file content into an `SignedTransaction` type. - + -- Decode JSON file content into a `SignedTransaction` type. let parsedSignedTransaction :: Either String Types.SignedTransaction parsedSignedTransaction = eitherDecode fileContent - signedTransaction <- case parsedSignedTransaction of + case parsedSignedTransaction of Right tx -> do logInfo ["Transaction in file: "] logInfo [[i| #{showPrettyJSON tx}.|]] - return tx - Left parseError -> logFatal [[i| Failed to decode file content into signedTransaction type: #{parseError}.|]] - -- Create the associated `AccountTransaction` type. - let encPayload = Types.encodePayload $ Types.stPayload signedTransaction - let header = Types.TransactionHeader (Types.stSigner signedTransaction) (Types.stNonce signedTransaction) (Types.stEnergy signedTransaction) (Types.payloadSize encPayload) (Types.stExpiryTime signedTransaction) - let signHash = Types.transactionSignHashFromHeaderPayload header encPayload + -- TODO: to further decode and display the `message` in contract update or init transactions, we need a schema. - -- TODO: to further decode the `message` in contract update or init transactions, we need a schema. - - return $ Types.AccountTransaction (Types.stSignature signedTransaction) header encPayload signHash + return tx + Left parseError -> logFatal [[i| Failed to decode file content into signedTransaction type: #{parseError}.|]] -- | Process a 'transaction ...' command. processTransactionCmd :: TransactionCmd -> Maybe FilePath -> Verbose -> Backend -> IO () @@ -729,15 +698,21 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - logSuccess ["transaction successfully completed"] + -- logSuccess ["transaction successfully completed"] TransactionSubmit fname intOpts -> do - accountTransaction <- readSignedTransactionFromFile fname + -- Read signedTransaction from file + signedTransaction <- readSignedTransactionFromFile fname + -- Confirm to submit transaction on chain when (ioConfirm intOpts) $ do confirmed <- askConfirmation $ Just "Do you want to send the transaction on chain? " unless confirmed exitTransactionCancelled - let tx = Types.NormalTransaction accountTransaction + -- Create the associated `bareBlockItem` + let encPayload = Types.encodePayload $ Types.stPayload signedTransaction + let header = Types.TransactionHeader (Types.stSigner signedTransaction) (Types.stNonce signedTransaction) (Types.stEnergy signedTransaction) (Types.payloadSize encPayload) (Types.stExpiryTime signedTransaction) + let signHash = Types.transactionSignHashFromHeaderPayload header encPayload + let tx = Types.NormalTransaction $ Types.AccountTransaction (Types.stSignature signedTransaction) header encPayload signHash withClient backend $ do -- Send transaction on chain @@ -755,19 +730,20 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - logSuccess ["transaction successfully completed"] + logSuccess ["transaction successfully completed"] TransactionAddSignature fname signers toKeys -> do - accountTransaction <- readSignedTransactionFromFile fname - -- Get encoded payload - let encPayload = Types.atrPayload accountTransaction + -- Read transaction from file + signedTransaction <- readSignedTransactionFromFile fname + + -- Create the encoded paylaod and header + let encPayload = Types.encodePayload $ Types.stPayload signedTransaction + let header = Types.TransactionHeader (Types.stSigner signedTransaction) (Types.stNonce signedTransaction) (Types.stEnergy signedTransaction) (Types.payloadSize encPayload) (Types.stExpiryTime signedTransaction) -- Extract accountKeyMap to be used to sign the transaction baseCfg <- getBaseConfig baseCfgDir verbose - let header = Types.atrHeader accountTransaction let signerAccountText = Text.pack $ show (Types.thSender header) - -- TODO: should we check if the `nonce` still makes sense as read from the file vs the one on-chain. - -- (and throw error if additional txs have been sent by account meanwhile meaning the account nonce is not valid anymore). + -- TODO: we could check if the `nonce` still makes sense as read from the file vs the one on-chain. keysArg <- case toKeys of Nothing -> do @@ -785,21 +761,16 @@ processTransactionCmd action baseCfgDir verbose backend = let sigBMap = Types.tsSignatures (Types.atrSignature transactionB) -- Extract the signature map A (original signatures as stored in the file) - let sigAMap = Types.tsSignatures (Types.atrSignature accountTransaction) + let sigAMap = Types.tsSignatures (Types.stSignature signedTransaction) -- Create the union of the signature map A and the signature map B let unionSignaturesMap = Map.unionWith Map.union sigAMap sigBMap -- Create final signed transaction including signtures A and B - let finalTransaction = accountTransaction{Types.atrSignature = Types.TransactionSignature unionSignaturesMap} - - -- Get protocol version - pv <- liftIO $ withClient backend $ do - cs <- getResponseValueOrDie =<< getConsensusInfo - return $ Queries.csProtocolVersion cs + let finalTransaction = signedTransaction{Types.stSignature = Types.TransactionSignature unionSignaturesMap} -- Write final signed transaction to file - liftIO $ writeSignedTransactionToFile finalTransaction fname verbose AllowOverwrite pv + liftIO $ writeSignedTransactionToFile finalTransaction fname verbose AllowOverwrite TransactionDeployCredential fname intOpts -> do source <- handleReadFile BSL.readFile fname withClient backend $ do @@ -1717,8 +1688,27 @@ signAndProcessTransaction verbose txCfg pl intOpts outFile backend = do cs <- getResponseValueOrDie =<< getConsensusInfo return $ Queries.csProtocolVersion cs - -- Write signed transaction to file - liftIO $ writeSignedTransactionToFile tx filePath verbose PromptBeforeOverwrite pv + -- Decode the payload from the transaction + let decPl = case Types.promoteProtocolVersion pv of + Types.SomeProtocolVersion spv -> Types.decodePayload spv (Types.payloadSize pl) pl + decPayload <- case decPl of + Right decPL -> return decPL + Left err -> logFatal ["Decoding of payload failed: " <> err] + + -- Create signedTransaction + let header = Types.atrHeader tx + let signedTransaction = + Types.SignedTransaction + Types.signedTransactionVersion + (Types.thEnergyAmount header) + (Types.thExpiry header) + (Types.thNonce header) + (Types.thSender header) + decPayload + (Types.atrSignature tx) + + -- Write signedTransaction to file + liftIO $ writeSignedTransactionToFile signedTransaction filePath verbose PromptBeforeOverwrite return Nothing Nothing -> do logInfo [[i| Send signed transaction to node.|]] diff --git a/transaction.json b/transaction.json new file mode 100644 index 00000000..2586a13f --- /dev/null +++ b/transaction.json @@ -0,0 +1,23 @@ +{ + "energy": 5000, + "expiryTime": 1715620915, + "nonce": 22, + "payload": { + "address": { + "index": 3383, + "subindex": 0 + }, + "amount": "0", + "message": "01000101420c0000000000000000000000000000", + "receiveName": "cis2-bridgeable.updateOperator", + "transactionType": "update" + }, + "signature": { + "0": { + "0": "0a5f99a512e42a6ed9ab8f94ad5fe0463f4cb00a7a65f4afcb39252432ecce5b8c03452d60e4290a8bc16093ba52c664b18559ede6d18ef48e156d0934f5cd02", + "1": "52271842cd8bacf60bcdea0e7226539079574cef512991bb57bbd09bb27fe16008552f4ce926cac16abbf8190a97264d96ad405a35bd35c466ef86c3ba15680c" + } + }, + "signer": "4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix", + "version": 1 +} \ No newline at end of file diff --git a/updateOperator.json b/updateOperator.json new file mode 100644 index 00000000..ac8ef764 --- /dev/null +++ b/updateOperator.json @@ -0,0 +1,15 @@ +[ + { + "operator": { + "Contract": [ + { + "index": 3138, + "subindex": 0 + } + ] + }, + "update": { + "Add": [] + } + } + ] From 65ea4b45ed273d801f58ef5e5048c33966c5bf2d Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 14 May 2024 15:45:43 +0300 Subject: [PATCH 15/22] Add comments --- deps/concordium-base | 2 +- src/Concordium/Client/Runner.hs | 10 +++++++--- transaction.json | 23 ----------------------- updateOperator.json | 15 --------------- 4 files changed, 8 insertions(+), 42 deletions(-) delete mode 100644 transaction.json delete mode 100644 updateOperator.json diff --git a/deps/concordium-base b/deps/concordium-base index 7baf5a3e..91ef10f5 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 7baf5a3ec97d49e63af7b44734799779dbce7340 +Subproject commit 91ef10f5f8d02d3ab2d07b86d8c9085281726620 diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 5eaa36db..630e0e1e 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -779,7 +779,7 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - logSuccess ["credential deployed successfully"] + -- logSuccess ["credential deployed successfully"] TransactionStatus h schemaFile -> do hash <- case parseTransactionHash h of Nothing -> logFatal [printf "invalid transaction hash '%s'" h] @@ -1654,6 +1654,7 @@ signAndProcessTransaction_ :: InteractionOpts -> -- | An optional file name to output the signed/partially-signed transaction to instead of sending it to the node Maybe FilePath -> + -- | Node backend connection Backend -> ClientMonad m () signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend = void $ signAndProcessTransaction verbose txCfg pl intOpts outFile backend @@ -1674,6 +1675,7 @@ signAndProcessTransaction :: InteractionOpts -> -- | An optional file name to output the signed/partially-signed transaction to instead of sending it to the node Maybe FilePath -> + -- | Node backend connection Backend -> ClientMonad m (Maybe TransactionStatusResult) signAndProcessTransaction verbose txCfg pl intOpts outFile backend = do @@ -1681,7 +1683,7 @@ signAndProcessTransaction verbose txCfg pl intOpts outFile backend = do case outFile of Just filePath -> do - logInfo [[i| Write signed transaction to file. Will not send it to the node.|]] + logInfo [[i| Will write signed transaction to file. Will not send transaction to the node.|]] -- Get protocol version pv <- liftIO $ withClient backend $ do @@ -1711,7 +1713,7 @@ signAndProcessTransaction verbose txCfg pl intOpts outFile backend = do liftIO $ writeSignedTransactionToFile signedTransaction filePath verbose PromptBeforeOverwrite return Nothing Nothing -> do - logInfo [[i| Send signed transaction to node.|]] + logInfo [[i| Will send signed transaction to node.|]] -- Send transaction on chain let bareBlockItem = Types.NormalTransaction tx @@ -3652,6 +3654,8 @@ processBakerRemoveCmd baseCfgDir verbose backend txOpts = do let payload = Types.encodePayload Types.RemoveBaker nrgCost _ = return . Just $ bakerRemoveEnergyCost $ Types.payloadSize payload txCfg@TransactionConfig{..} <- getTransactionCfg baseCfg txOpts nrgCost + logInfo ["payloadpayloadpayloadpayloadpayloadpayload: "] + logInfo [[i| #{showPrettyJSON payload}.|]] logSuccess ( [ printf "submitting transaction to remove validator with %s" (show (naAddr $ esdAddress tcEncryptedSigningData)), printf "allowing up to %s to be spent as transaction fee" (showNrg tcEnergy) diff --git a/transaction.json b/transaction.json deleted file mode 100644 index 2586a13f..00000000 --- a/transaction.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "energy": 5000, - "expiryTime": 1715620915, - "nonce": 22, - "payload": { - "address": { - "index": 3383, - "subindex": 0 - }, - "amount": "0", - "message": "01000101420c0000000000000000000000000000", - "receiveName": "cis2-bridgeable.updateOperator", - "transactionType": "update" - }, - "signature": { - "0": { - "0": "0a5f99a512e42a6ed9ab8f94ad5fe0463f4cb00a7a65f4afcb39252432ecce5b8c03452d60e4290a8bc16093ba52c664b18559ede6d18ef48e156d0934f5cd02", - "1": "52271842cd8bacf60bcdea0e7226539079574cef512991bb57bbd09bb27fe16008552f4ce926cac16abbf8190a97264d96ad405a35bd35c466ef86c3ba15680c" - } - }, - "signer": "4jxvYasaPncfmCFCLZCvuL5cZuvR5HAQezCHZH7ZA7AGsRYpix", - "version": 1 -} \ No newline at end of file diff --git a/updateOperator.json b/updateOperator.json deleted file mode 100644 index ac8ef764..00000000 --- a/updateOperator.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "operator": { - "Contract": [ - { - "index": 3138, - "subindex": 0 - } - ] - }, - "update": { - "Add": [] - } - } - ] From 0124024c52096fcd000bf58aab4bb8a03e2df9eb Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 14 May 2024 17:21:14 +0300 Subject: [PATCH 16/22] Update changeLog --- ChangeLog.md | 4 ++++ src/Concordium/Client/Commands.hs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index f382e81e..38be96be 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,10 @@ ## Unreleased +- Rename subcommand `TransactionSubmit` to `TransactionSignAndSubmit`. +- Add subcommand `TransactionSubmit` to submit a signed transaction on chain. +- Add subcommand `TransactionAddSignature` to add a signature to a partially-signed transaction. +- Add optional `--outFile` flag to all transaction-creating commands to output a partially-singed transaction to a file. - Update GHC version to 9.6.4 (lts-22.9). - Update Rust version to 1.73. - Support protocol version 7. diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 0f2cf5ef..e6898348 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -791,7 +791,7 @@ expectedSignedTransactionFormat :: [String] expectedSignedTransactionFormat = [ " {", " \"energy\": 5000,", - " \"expiry\": 1715188938,", + " \"expiry\": \"2024-05-14T13:48:28Z+00:00\",", " \"nonce\": 12,", " \"payload\": {", " \"address\": {", From 2b3fd6e4adceb0af90a1ebcfba905232f26af672 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 15 May 2024 12:17:06 +0300 Subject: [PATCH 17/22] Small updates --- deps/concordium-base | 2 +- src/Concordium/Client/Commands.hs | 2 +- src/Concordium/Client/Runner.hs | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 91ef10f5..0c58cb15 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 91ef10f5f8d02d3ab2d07b86d8c9085281726620 +Subproject commit 0c58cb158c7b7417e12dcb2ff24fbdc71dca2050 diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index e6898348..c037c938 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -791,7 +791,7 @@ expectedSignedTransactionFormat :: [String] expectedSignedTransactionFormat = [ " {", " \"energy\": 5000,", - " \"expiry\": \"2024-05-14T13:48:28Z+00:00\",", + " \"expiry\": 1715708777,", " \"nonce\": 12,", " \"payload\": {", " \"address\": {", diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 630e0e1e..cc3571df 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -1701,7 +1701,6 @@ signAndProcessTransaction verbose txCfg pl intOpts outFile backend = do let header = Types.atrHeader tx let signedTransaction = Types.SignedTransaction - Types.signedTransactionVersion (Types.thEnergyAmount header) (Types.thExpiry header) (Types.thNonce header) From 47767484efa504fd4e849292cacdd81ab3e92cd6 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 17 May 2024 16:26:55 +0300 Subject: [PATCH 18/22] Update submodule link --- deps/concordium-base | 2 +- stack.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 0c58cb15..b1b795c1 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 0c58cb158c7b7417e12dcb2ff24fbdc71dca2050 +Subproject commit b1b795c17a815959b2e6ecc1afd6c5085e201501 diff --git a/stack.yaml b/stack.yaml index 179176f4..deaf9504 100644 --- a/stack.yaml +++ b/stack.yaml @@ -18,7 +18,6 @@ # resolver: ./custom-snapshot.yaml # resolver: https://example.com/snapshots/2018-01-01.yaml resolver: lts-22.9 -system-ghc: true # User packages to be built. # Various formats can be used as shown in the example below. From be58580c723d04e5dcca3fdb5e70d59f099999fc Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 21 May 2024 15:49:19 +0300 Subject: [PATCH 19/22] Address comments --- deps/concordium-base | 2 +- src/Concordium/Client/Runner.hs | 24 ++++---- src/Concordium/Client/Types/Transaction.hs | 71 +++++++++++++++++++++- test/SimpleClientTests/TransactionSpec.hs | 37 ++++++++++- 4 files changed, 118 insertions(+), 16 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index b1b795c1..558b094e 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit b1b795c17a815959b2e6ecc1afd6c5085e201501 +Subproject commit 558b094e1ca5c1ade5d10fcc85be70cd647b43f2 diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index cc3571df..8d698435 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -650,7 +650,7 @@ getTxContractInfoWithSchemas schemaFile status = do MultipleBlocksAmbiguous bhts -> map (second f) bhts -- | Write a `SignedTransaction` to a JSON file. -writeSignedTransactionToFile :: Types.SignedTransaction -> FilePath -> Bool -> OverwriteSetting -> IO () +writeSignedTransactionToFile :: CT.SignedTransaction -> FilePath -> Bool -> OverwriteSetting -> IO () writeSignedTransactionToFile signedTransaction outFile verbose overwriteSetting = do let txJson = AE.encodePretty signedTransaction success <- liftIO $ handleWriteFile BSL.writeFile overwriteSetting verbose outFile txJson @@ -661,12 +661,12 @@ writeSignedTransactionToFile signedTransaction outFile verbose overwriteSetting -- | Read and display a `SignedTransaction` from a JSON file. -- Returns the associated `SignedTransaction`. -readSignedTransactionFromFile :: FilePath -> IO Types.SignedTransaction +readSignedTransactionFromFile :: FilePath -> IO CT.SignedTransaction readSignedTransactionFromFile fname = do fileContent <- liftIO $ BSL8.readFile fname -- Decode JSON file content into a `SignedTransaction` type. - let parsedSignedTransaction :: Either String Types.SignedTransaction + let parsedSignedTransaction :: Either String CT.SignedTransaction parsedSignedTransaction = eitherDecode fileContent case parsedSignedTransaction of Right tx -> do @@ -709,10 +709,10 @@ processTransactionCmd action baseCfgDir verbose backend = unless confirmed exitTransactionCancelled -- Create the associated `bareBlockItem` - let encPayload = Types.encodePayload $ Types.stPayload signedTransaction - let header = Types.TransactionHeader (Types.stSigner signedTransaction) (Types.stNonce signedTransaction) (Types.stEnergy signedTransaction) (Types.payloadSize encPayload) (Types.stExpiryTime signedTransaction) + let encPayload = Types.encodePayload $ CT.stPayload signedTransaction + let header = Types.TransactionHeader (CT.stSigner signedTransaction) (CT.stNonce signedTransaction) (CT.stEnergy signedTransaction) (Types.payloadSize encPayload) (CT.stExpiryTime signedTransaction) let signHash = Types.transactionSignHashFromHeaderPayload header encPayload - let tx = Types.NormalTransaction $ Types.AccountTransaction (Types.stSignature signedTransaction) header encPayload signHash + let tx = Types.NormalTransaction $ Types.AccountTransaction (CT.stSignature signedTransaction) header encPayload signHash withClient backend $ do -- Send transaction on chain @@ -736,8 +736,8 @@ processTransactionCmd action baseCfgDir verbose backend = signedTransaction <- readSignedTransactionFromFile fname -- Create the encoded paylaod and header - let encPayload = Types.encodePayload $ Types.stPayload signedTransaction - let header = Types.TransactionHeader (Types.stSigner signedTransaction) (Types.stNonce signedTransaction) (Types.stEnergy signedTransaction) (Types.payloadSize encPayload) (Types.stExpiryTime signedTransaction) + let encPayload = Types.encodePayload $ CT.stPayload signedTransaction + let header = Types.TransactionHeader (CT.stSigner signedTransaction) (CT.stNonce signedTransaction) (CT.stEnergy signedTransaction) (Types.payloadSize encPayload) (CT.stExpiryTime signedTransaction) -- Extract accountKeyMap to be used to sign the transaction baseCfg <- getBaseConfig baseCfgDir verbose @@ -761,13 +761,13 @@ processTransactionCmd action baseCfgDir verbose backend = let sigBMap = Types.tsSignatures (Types.atrSignature transactionB) -- Extract the signature map A (original signatures as stored in the file) - let sigAMap = Types.tsSignatures (Types.stSignature signedTransaction) + let sigAMap = Types.tsSignatures (CT.stSignature signedTransaction) -- Create the union of the signature map A and the signature map B let unionSignaturesMap = Map.unionWith Map.union sigAMap sigBMap -- Create final signed transaction including signtures A and B - let finalTransaction = signedTransaction{Types.stSignature = Types.TransactionSignature unionSignaturesMap} + let finalTransaction = signedTransaction{CT.stSignature = Types.TransactionSignature unionSignaturesMap} -- Write final signed transaction to file liftIO $ writeSignedTransactionToFile finalTransaction fname verbose AllowOverwrite @@ -1700,7 +1700,7 @@ signAndProcessTransaction verbose txCfg pl intOpts outFile backend = do -- Create signedTransaction let header = Types.atrHeader tx let signedTransaction = - Types.SignedTransaction + CT.SignedTransaction (Types.thEnergyAmount header) (Types.thExpiry header) (Types.thNonce header) @@ -3653,8 +3653,6 @@ processBakerRemoveCmd baseCfgDir verbose backend txOpts = do let payload = Types.encodePayload Types.RemoveBaker nrgCost _ = return . Just $ bakerRemoveEnergyCost $ Types.payloadSize payload txCfg@TransactionConfig{..} <- getTransactionCfg baseCfg txOpts nrgCost - logInfo ["payloadpayloadpayloadpayloadpayloadpayload: "] - logInfo [[i| #{showPrettyJSON payload}.|]] logSuccess ( [ printf "submitting transaction to remove validator with %s" (show (naAddr $ esdAddress tcEncryptedSigningData)), printf "allowing up to %s to be spent as transaction fee" (showNrg tcEnergy) diff --git a/src/Concordium/Client/Types/Transaction.hs b/src/Concordium/Client/Types/Transaction.hs index ac89e661..7e67a4a0 100644 --- a/src/Concordium/Client/Types/Transaction.hs +++ b/src/Concordium/Client/Types/Transaction.hs @@ -10,7 +10,7 @@ import qualified Concordium.Cost as Cost import Concordium.Crypto.EncryptedTransfers import qualified Concordium.ID.Types as IDTypes import Concordium.Types -import Concordium.Types.Execution (bakerKeysWithProofsSize) +import Concordium.Types.Execution as Types import qualified Concordium.Types.Transactions as Types import Data.Aeson as AE @@ -349,3 +349,72 @@ instance AE.FromJSON TransactionJSON where tPayload <- v .: "payload" keyMap <- v .: "keys" return $ TransactionJSON tHeader tPayload keyMap + +----------------------------------------------------------------- + +-- * JSON representation of a signed/partially-signed transaction + +----------------------------------------------------------------- + +-- | A 'SignedTransaction' is a transaction that is signed by an account (the signer) +-- with some keys. The representation might be a fully signed transaction ready to be +-- sent on-chain or a partially-signed transaction that needs additional signatures +-- added to be ready to be sent on-chain. +-- +-- The `ToJSON` instance has the purpose converting the object into a human-readable +-- representation ready to be printed into a JSON file. This file can be shared among +-- different tools of the Concordium ecosystem for adding additional signatures. +-- +-- The chosen representation is the minimal necessary data needed to construct the +-- 'TransactionSignHash' which is the value that is signed by the signer. The +-- 'TransactionSignHash' and 'payloadSize' should be re-computed when processing a +-- 'SignedTransaction' (e.g. when adding signatures or sending the transaction on-chain). +data SignedTransaction = SignedTransaction + { -- | Amount of energy dedicated to the execution of this transaction. + stEnergy :: !Energy, + -- | Absolute expiration time after which transaction will not be executed. + stExpiryTime :: !TransactionExpiryTime, + -- | Account nonce. + stNonce :: !Nonce, + -- | Signer account address. + stSigner :: !AccountAddress, + -- | The payload of the transaction. + stPayload :: !Types.Payload, + -- | Signatures generated by the signer account. This map might contain enough signatures to send the transaction on-chain or + -- additional signatures are needed before the transaction is considered fully signed. + stSignature :: !Types.TransactionSignature + } + deriving (Eq, Show) + +-- | Implement `FromJSON` and `ToJSON` instances for `SignedTransaction`. +instance ToJSON SignedTransaction where + toJSON SignedTransaction{..} = + AE.object + [ "version" AE..= signedTransactionVersion, + "energy" AE..= stEnergy, + "expiryTime" AE..= stExpiryTime, + "nonce" AE..= stNonce, + "signer" AE..= stSigner, + "payload" AE..= stPayload, + "signature" AE..= stSignature + ] + +-- Implement `FromJSON` instance for `SignedTransaction`. +instance FromJSON SignedTransaction where + parseJSON = AE.withObject "SignedTransaction" $ \obj -> do + stVersion <- obj AE..: "version" + if stVersion /= signedTransactionVersion + then fail $ "Unexpected version: " ++ show stVersion + else do + stEnergy <- obj AE..: "energy" + stExpiryTime <- obj AE..: "expiryTime" + stNonce <- obj AE..: "nonce" + stSigner <- obj AE..: "signer" + stSignature <- obj AE..: "signature" + stPayload <- obj AE..: "payload" + return SignedTransaction{..} + +-- | The initial version of the above `SignedTransaction` JSON representation. +-- The version will be incremented when introducing a new format in the future. +signedTransactionVersion :: Int +signedTransactionVersion = 1 diff --git a/test/SimpleClientTests/TransactionSpec.hs b/test/SimpleClientTests/TransactionSpec.hs index 462b6349..f4cd105b 100644 --- a/test/SimpleClientTests/TransactionSpec.hs +++ b/test/SimpleClientTests/TransactionSpec.hs @@ -2,8 +2,10 @@ module SimpleClientTests.TransactionSpec where import Concordium.Client.Output import Concordium.Client.Runner +import Concordium.Client.Types.Transaction import Concordium.Client.Types.TransactionStatus +import qualified Concordium.Crypto.SignatureScheme as ID import qualified Concordium.ID.Types as IDTypes import qualified Concordium.Types as Types import qualified Concordium.Types.Execution as Types @@ -11,9 +13,13 @@ import qualified Concordium.Types.Execution as Types import SimpleClientTests.QueryTransaction import Control.Monad.Writer +import qualified Data.Aeson as AE +import Data.ByteString.Short as SBS import Data.Map.Strict as Map -import Data.Text (Text) +import Data.Text as Text +import Data.Word (Word8) +import Concordium.Types.Transactions import Test.Hspec hiding (pending) exampleAddress1 :: Text @@ -36,6 +42,32 @@ exampleAccountAddr2 = case IDTypes.addressFromText exampleAddress2 of -- of the text is that of a valid address. Left err -> error err +exampleRegisterDataPayload :: Types.Payload +exampleRegisterDataPayload = Types.RegisterData{rdData = Types.RegisteredData exampleShortByteString} + +exampleShortByteString :: ShortByteString +exampleShortByteString = SBS.pack ([1, 2] :: [Word8]) + +exampleSignatureMap :: Map.Map IDTypes.KeyIndex ID.Signature +exampleSignatureMap = Map.singleton 1 (ID.Signature exampleShortByteString) + +exampleCredentialSignatureMapEmpty :: Map.Map IDTypes.CredentialIndex (Map.Map IDTypes.KeyIndex ID.Signature) +exampleCredentialSignatureMapEmpty = Map.empty + +exampleCredentialSignatureMap :: Map.Map IDTypes.CredentialIndex (Map.Map IDTypes.KeyIndex ID.Signature) +exampleCredentialSignatureMap = Map.insert (1 :: IDTypes.CredentialIndex) exampleSignatureMap exampleCredentialSignatureMapEmpty + +exampleSignedTransaction :: SignedTransaction +exampleSignedTransaction = + SignedTransaction + { stEnergy = Types.Energy 1, + stExpiryTime = Types.TransactionTime 2, + stNonce = Types.Nonce 3, + stSigner = exampleAccountAddr1, + stPayload = exampleRegisterDataPayload, + stSignature = TransactionSignature exampleCredentialSignatureMap + } + exampleTransactionHash :: Types.TransactionHash exampleTransactionHash = read "c20911f59cda41c116f528531e815a3b561861b96014b379e8a52f1cbbafd2e4" @@ -217,6 +249,9 @@ awaitStateTests = describe "await state" $ do specify "correct final state" $ finalState `shouldBe` finalized specify "wait called 3 times" $ waitCount `shouldBe` 3 + describe "JSON encoding and decoding" $ do + specify "for 'SignedTransaction'" $ (AE.eitherDecode . AE.encode $ exampleSignedTransaction) `shouldBe` Right exampleSignedTransaction + printTransactionStatusTests :: Spec printTransactionStatusTests = describe "print transaction status" $ do -- Expected cases. From 72eece2ee511503ab8ae62f4590ed7e8ce6ddc83 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 21 May 2024 18:20:28 +0300 Subject: [PATCH 20/22] Address comments --- deps/concordium-base | 2 +- src/Concordium/Client/Commands.hs | 12 ++---------- src/Concordium/Client/Runner.hs | 8 ++++---- src/Concordium/Client/Types/Transaction.hs | 2 +- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 558b094e..84de5de8 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 558b094e1ca5c1ade5d10fcc85be70cd647b43f2 +Subproject commit 84de5de807b95c9103866fad25a76ac5eb8e2383 diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index c037c938..1c0c401b 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -255,9 +255,7 @@ data AccountCmd AccountEncrypt { aeTransactionOpts :: !(TransactionOpts (Maybe Energy)), -- | Amount to transfer from public to encrypted balance. - aeAmount :: !Amount, - -- | Optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain. - aeOutFile :: !(Maybe FilePath) + aeAmount :: !Amount } | -- | Transfer part of the encrypted balance to the public balance of the -- account. @@ -267,16 +265,13 @@ data AccountCmd adAmount :: !Amount, -- | Which indices of incoming amounts to use as inputs. -- If none are provided all existing ones will be used. - adIndex :: !(Maybe Int), - -- | Optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain. - adOutFile :: !(Maybe FilePath) + adIndex :: !(Maybe Int) } | -- | Updated credentials and account threshold (i.e. how many credential holders that need to sign transactions) AccountUpdateCredentials { aucNewCredInfos :: !(Maybe FilePath), -- File containing the new CredentialDeploymentInformation's aucRemoveCredIds :: !(Maybe FilePath), -- File containing the CredentialRegistrationID's for the credentials to be removed aucNewThreshold :: !AccountThreshold, -- The new account threshold - aucOutFile :: !(Maybe FilePath), -- Otional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain. aucTransactionOpts :: !(TransactionOpts (Maybe Energy)) } | -- | Show an alias for the account. @@ -1002,7 +997,6 @@ accountEncryptCmd = ( AccountEncrypt <$> transactionOptsParser <*> option (eitherReader amountFromStringInform) (long "amount" <> metavar "CCD-AMOUNT" <> help "The amount to transfer to shielded balance.") - <*> optional (strOption (long "outFile" <> metavar "FILE" <> help "An optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) ) (progDesc "Transfer an amount from public to shielded balance of the account.") ) @@ -1016,7 +1010,6 @@ accountDecryptCmd = <$> transactionOptsParser <*> option (maybeReader amountFromString) (long "amount" <> metavar "CCD-AMOUNT" <> help "The amount to transfer to public balance.") <*> optional (option auto (long "index" <> metavar "INDEX" <> help "Optionally specify the index up to which shielded amounts should be combined.")) - <*> optional (strOption (long "outFile" <> metavar "FILE" <> help "An optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) ) (progDesc "Transfer an amount from shielded to public balance of the account.") ) @@ -1058,7 +1051,6 @@ accountUpdateCredentialsCmd = <$> optional (strOption (long "new-credentials" <> metavar "FILE" <> help "File containing the new credential deployment informations.")) <*> optional (strOption (long "remove-credentials" <> metavar "FILE" <> help "File containing credential registration ids of the credentials to be removed.")) <*> option auto (long "new-threshold" <> metavar "THRESHOLD" <> help "New account threshold, i.e. how many credential holders needed to sign a transaction.") - <*> optional (strOption (long "outFile" <> metavar "FILE" <> help "An optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) <*> transactionOptsParser ) ( progDescDoc $ diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 8d698435..0b5e4272 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -1936,7 +1936,7 @@ processAccountCmd action baseCfgDir verbose backend = let outFile = toOutFile txOpts liftIO $ credentialUpdateKeysTransactionConfirm aukCfg (ioConfirm intOpts) signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend - AccountUpdateCredentials cdisFile removeCidsFile newThreshold outFile txOpts -> do + AccountUpdateCredentials cdisFile removeCidsFile newThreshold txOpts -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -1965,7 +1965,7 @@ processAccountCmd action baseCfgDir verbose backend = auctcNewThreshold = newThreshold } liftIO $ accountUpdateCredentialsTransactionConfirm aucCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg epayload intOpts outFile backend + signAndProcessTransaction_ verbose txCfg epayload intOpts (toOutFile txOpts) backend AccountEncrypt{..} -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -1982,7 +1982,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts aeTransactionOpts accountEncryptTransactionConfirm aetxCfg (ioConfirm intOpts) - withClient backend $ signAndProcessTransaction_ verbose txCfg pl intOpts aeOutFile backend + withClient backend $ signAndProcessTransaction_ verbose txCfg pl intOpts (toOutFile aeTransactionOpts) backend AccountDecrypt{..} -> do baseCfg <- getBaseConfig baseCfgDir verbose when verbose $ do @@ -2014,7 +2014,7 @@ processAccountCmd action baseCfgDir verbose backend = let intOpts = toInteractionOpts adTransactionOpts accountDecryptTransactionConfirm adtxCfg (ioConfirm intOpts) - signAndProcessTransaction_ verbose txCfg pl intOpts adOutFile backend + signAndProcessTransaction_ verbose txCfg pl intOpts (toOutFile adTransactionOpts) backend AccountShowAlias addrOrName alias -> do baseCfg <- getBaseConfig baseCfgDir verbose case getAccountAddress (bcAccountNameMap baseCfg) addrOrName of diff --git a/src/Concordium/Client/Types/Transaction.hs b/src/Concordium/Client/Types/Transaction.hs index 7e67a4a0..1471035e 100644 --- a/src/Concordium/Client/Types/Transaction.hs +++ b/src/Concordium/Client/Types/Transaction.hs @@ -386,7 +386,7 @@ data SignedTransaction = SignedTransaction } deriving (Eq, Show) --- | Implement `FromJSON` and `ToJSON` instances for `SignedTransaction`. +-- | Implement `ToJSON` instance for `SignedTransaction`. instance ToJSON SignedTransaction where toJSON SignedTransaction{..} = AE.object From 34deed3a54e3d481d6a757369d986a2486de90ce Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 24 May 2024 12:00:58 +0300 Subject: [PATCH 21/22] Address comments --- deps/concordium-base | 2 +- src/Concordium/Client/Commands.hs | 8 +++++--- src/Concordium/Client/Runner.hs | 8 ++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 84de5de8..377398d9 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 84de5de807b95c9103866fad25a76ac5eb8e2383 +Subproject commit 377398d920e8b09f4105287de52f69f013af1c00 diff --git a/src/Concordium/Client/Commands.hs b/src/Concordium/Client/Commands.hs index 1c0c401b..64fa96af 100644 --- a/src/Concordium/Client/Commands.hs +++ b/src/Concordium/Client/Commands.hs @@ -435,7 +435,9 @@ data TransactionOpts energyOrMaybe = TransactionOpts toNonce :: !(Maybe Nonce), toMaxEnergyAmount :: !energyOrMaybe, toExpiration :: !(Maybe Text), - toOutFile :: !(Maybe FilePath), -- Optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain. + -- | Optional file name and path to output the signed/partially-signed + -- transaction to instead of submitting the transaction on-chain. + toOutFile :: !(Maybe FilePath), toInteractionOpts :: !InteractionOpts } deriving (Show) @@ -659,7 +661,7 @@ transactionOptsParserBuilder energyOrMaybeParser = <*> optional (option auto (long "nonce" <> metavar "NONCE" <> help "Transaction nonce.")) <*> energyOrMaybeParser <*> optional (strOption (long "expiry" <> metavar "EXPIRY" <> help "Expiration time of a transaction, specified as a relative duration (\"30s\", \"5m\", etc.) or UNIX epoch timestamp.")) - <*> optional (strOption (long "outFile" <> metavar "FILE" <> help "An optional file name and path to ouptput the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) + <*> optional (strOption (long "out" <> metavar "FILE" <> help "File to output the signed/partially-signed transaction to instead of submitting the transaction on-chain.")) <*> interactionOptsParser interactionOptsParser :: Parser InteractionOpts @@ -734,7 +736,7 @@ transactionSignAndSubmitCmd = <$> strArgument (metavar "FILE" <> help "File containing the transaction parameters in JSON format.") <*> interactionOptsParser ) - (progDesc "Create transaction, sign it, and send it to the node.") + (progDesc "Parse a JSON transaction with keys, sign it, and send it to the node.") ) transactionSubmitCmd :: Mod CommandFields TransactionCmd diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index 0b5e4272..fd785a07 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -673,6 +673,11 @@ readSignedTransactionFromFile fname = do logInfo ["Transaction in file: "] logInfo [[i| #{showPrettyJSON tx}.|]] + -- Check if the transaction is expired. + now <- getCurrentTimeUnix + let expiry = CT.stExpiryTime tx + warnSuspiciousExpiry expiry now + -- TODO: to further decode and display the `message` in contract update or init transactions, we need a schema. return tx @@ -698,7 +703,6 @@ processTransactionCmd action baseCfgDir verbose backend = logSuccess [printf "transaction '%s' sent to the node" (show hash)] when (ioTail intOpts) $ do tailTransaction_ verbose hash - -- logSuccess ["transaction successfully completed"] TransactionSubmit fname intOpts -> do -- Read signedTransaction from file signedTransaction <- readSignedTransactionFromFile fname @@ -1660,7 +1664,7 @@ signAndProcessTransaction_ :: signAndProcessTransaction_ verbose txCfg pl intOpts outFile backend = void $ signAndProcessTransaction verbose txCfg pl intOpts outFile backend -- | Sign a transaction and process transaction by either send it to the node or write it to a file. --- If send to the node, optionally tail it (see 'tailTransaction' below). +-- If sent to the node, optionally tail it (see 'tailTransaction' below). -- If tailed, it returns the TransactionStatusResult of the finalized status, -- otherwise the return value is @Nothing@. signAndProcessTransaction :: From b423ec54391b656c750320bd78bf469225d48caf Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 24 May 2024 13:54:19 +0300 Subject: [PATCH 22/22] Update submodule link --- ChangeLog.md | 8 ++++---- deps/concordium-base | 2 +- src/Concordium/Client/Runner.hs | 2 +- src/Concordium/Client/Types/Transaction.hs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 38be96be..c637f723 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,7 +5,7 @@ - Rename subcommand `TransactionSubmit` to `TransactionSignAndSubmit`. - Add subcommand `TransactionSubmit` to submit a signed transaction on chain. - Add subcommand `TransactionAddSignature` to add a signature to a partially-signed transaction. -- Add optional `--outFile` flag to all transaction-creating commands to output a partially-singed transaction to a file. +- Add optional `--out` flag to all transaction-creating commands to output a partially-singed transaction to a file. - Update GHC version to 9.6.4 (lts-22.9). - Update Rust version to 1.73. - Support protocol version 7. @@ -62,7 +62,7 @@ - Add round and epoch to the output of `raw GetBlockInfo`. - Add round and epoch to the output of `block show` when they are present. - Print "Block time" instead of "Slot time" in the output of `block show`. -- In the output of `consensus show-parameters`, only print election difficulty when present. +- In the output of `consensus show-parameters`, only print election difficulty when present. ## 5.2.0 @@ -125,10 +125,10 @@ - Add support of contract schema V3. - V3 schemas offer the same options as V2, but also optionally includes a schema for contract events. - - `transaction status` now displays contract events, and a schema can be provided with `--schema`, which + - `transaction status` now displays contract events, and a schema can be provided with `--schema`, which will be used to parse the contract events. By default events are parsed with the schema embedded in the contract, if present. - - This enables concordium-client to interact with contracts and schemas + - This enables concordium-client to interact with contracts and schemas using `concordium-std` version 5. - Improved formatting of `transaction status` output using contract schemas if they are available for displaying contract events. diff --git a/deps/concordium-base b/deps/concordium-base index 377398d9..1c97f303 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 377398d920e8b09f4105287de52f69f013af1c00 +Subproject commit 1c97f30317960427e59e2643bd55fd8f6f26697a diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index fd785a07..989c45ff 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -1696,7 +1696,7 @@ signAndProcessTransaction verbose txCfg pl intOpts outFile backend = do -- Decode the payload from the transaction let decPl = case Types.promoteProtocolVersion pv of - Types.SomeProtocolVersion spv -> Types.decodePayload spv (Types.payloadSize pl) pl + Types.SomeProtocolVersion spv -> Types.decodePayload spv pl decPayload <- case decPl of Right decPL -> return decPL Left err -> logFatal ["Decoding of payload failed: " <> err] diff --git a/src/Concordium/Client/Types/Transaction.hs b/src/Concordium/Client/Types/Transaction.hs index 1471035e..ca63f13c 100644 --- a/src/Concordium/Client/Types/Transaction.hs +++ b/src/Concordium/Client/Types/Transaction.hs @@ -367,8 +367,8 @@ instance AE.FromJSON TransactionJSON where -- -- The chosen representation is the minimal necessary data needed to construct the -- 'TransactionSignHash' which is the value that is signed by the signer. The --- 'TransactionSignHash' and 'payloadSize' should be re-computed when processing a --- 'SignedTransaction' (e.g. when adding signatures or sending the transaction on-chain). +-- 'TransactionSignHash' should be re-computed when processing a 'SignedTransaction' +-- (e.g. when adding signatures or sending the transaction on-chain). data SignedTransaction = SignedTransaction { -- | Amount of energy dedicated to the execution of this transaction. stEnergy :: !Energy,