{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
module VerifySpec (spec) where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Data.Time.Format (defaultTimeLocale, formatTime)
import Data.Time.LocalTime (TimeZone (..), utcToZonedTime)
import qualified OpenTimestamps.DetachedTimestampFile as DTSF
import OpenTimestamps.Verify
( BitcoinConfig (..)
, verifyDetachedTimestampFile
)
import System.Directory (doesFileExist, getHomeDirectory)
import System.FilePath ((</>))
import System.IO (hPutStrLn, stderr)
import Test.Hspec
( Spec
, describe
, expectationFailure
, it
, shouldSatisfy
)
-- | RPC credentials for Bitcoin node
data RPCCredentials where
RPCCredentials ::
{ rpcUser :: T.Text
, rpcPassword :: T.Text
} ->
RPCCredentials
deriving (Show, Eq)
-- | Reads RPC credentials from a Bitcoin cookie file
readCookieFile :: FilePath -> IO (Maybe RPCCredentials)
readCookieFile path = do
exists <- doesFileExist path
if exists
then do
content <- TIO.readFile path
case T.splitOn (T.pack ":") content of
[user, pass] -> pure $ Just $ RPCCredentials user pass
_ -> pure Nothing
else do
hPutStrLn stderr $ "Cookie file not found at: " ++ path
pure Nothing
getBitcoinConfig :: IO BitcoinConfig
getBitcoinConfig = do
homeDir <- getHomeDirectory
let cookieFilePath = homeDir </> ".bitcoin" </> ".cookie"
mrpcc <- readCookieFile cookieFilePath
config <- case mrpcc of
Nothing -> do
hPutStrLn
stderr
"Error: Bitcoin cookie file not found. A local Bitcoin node is required for this test to work."
hPutStrLn stderr $ "Expected cookie file at: " ++ cookieFilePath
pure $
BitcoinConfig
{ bitcoinHost = "localhost"
, bitcoinPort = 8332
, bitcoinUser = "testuser"
, bitcoinPass = "testpass"
}
Just rpcc ->
pure $
BitcoinConfig
{ bitcoinHost = "localhost"
, bitcoinPort = 8332
, bitcoinUser = T.unpack $ rpcUser rpcc
, bitcoinPass = T.unpack $ rpcPassword rpcc
}
putStrLn $ "Using Bitcoin Config: " ++ show config
pure config
spec :: Spec
spec = describe "OpenTimestamps.Verify" $ do
describe "verifyDetachedTimestampFile with incomplete.txt.ots" $ do
it "should indicate verify success for incomplete.txt.ots" $ do
otsFileContent <- BS.readFile "examples/incomplete.txt.ots"
let dtsfEither = DTSF.deserialize (BSL.fromStrict otsFileContent)
case dtsfEither of
Left err -> expectationFailure $ "Failed to deserialize OTS file: " ++ err
Right dtsf -> do
let fileHash = B16.decodeLenient (BS8.pack "05c4f616a8e5310d19d938cfd769864d7f4ccdc2ca8b479b10af83564b097af9")
let calendarUrls = [] -- No calendar URLs needed for this test
bitcoinConfig <- getBitcoinConfig
result <- verifyDetachedTimestampFile bitcoinConfig calendarUrls dtsf fileHash
case result of
Right (blockHeight, utcTime) -> do
-- TODO Hardcoded CEST (UTC+2), so -2 hours from UTC
let zonedTime = utcToZonedTime (TimeZone (-(1 * 60 * 4)) False "CEST") utcTime
let formattedTime = formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S %Z" zonedTime
putStrLn $
"Success! Bitcoin block " ++ show blockHeight ++ " attests existence as of " ++ formattedTime
_ -> putStrLn "Failed"
result
`shouldSatisfy` ( \case
Right _ -> True
_ -> False
)