mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 08:37:30 +00:00
store: remove the taken-order code
This commit is contained in:
1 parent
e6fb909d61
commit
f08763924c
16 files changed
+59
-466
No files matched your search
@@ -1,6 +1,5 @@
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module StoreService.Names.Abi
|
||||
( AbiValue (..),
|
||||
@@ -21,7 +20,6 @@ module StoreService.Names.Abi
|
||||
decodeBytes32,
|
||||
decodeBytes,
|
||||
decodeString,
|
||||
decodePrices,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -126,15 +124,6 @@ decodeBytes ret = offsetAt 0 ret >>= bytesAt ret
|
||||
decodeString :: ByteString -> Either AbiError Text
|
||||
decodeString ret = decodeBytes ret >>= first (const AENotUtf8) . decodeUtf8'
|
||||
|
||||
-- | Decodes @(uint256, (uint256, uint256)[])@, the oracle's base price and its prices by label length.
|
||||
decodePrices :: ByteString -> Either AbiError (Integer, [(Integer, Integer)])
|
||||
decodePrices ret = do
|
||||
base <- uintAt 0 ret
|
||||
items <- offsetAt abiWordSize ret
|
||||
n <- offsetAt items ret
|
||||
let pairAt i = let o = items + abiWordSize * (1 + 2 * i) in (,) <$> uintAt o ret <*> uintAt (o + abiWordSize) ret
|
||||
(base,) <$> mapM pairAt [0 .. n - 1]
|
||||
|
||||
wordAt :: Int -> ByteString -> Either AbiError ByteString
|
||||
wordAt o ret
|
||||
| o + abiWordSize <= B.length ret = Right $ B.take abiWordSize $ B.drop o ret
|
||||
|
||||
@@ -14,7 +14,6 @@ module StoreService.Names.Contracts
|
||||
nameRecords,
|
||||
textRecord,
|
||||
chainPriceCents,
|
||||
codeLengthFor,
|
||||
sponsorNonce,
|
||||
resolverNonce,
|
||||
confirmedTag,
|
||||
@@ -33,12 +32,10 @@ import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Functor.Compose (Compose (..))
|
||||
import Data.IORef (IORef, atomicWriteIORef, newIORef, readIORef)
|
||||
import qualified Data.Map.Strict as M
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
|
||||
import Data.Word (Word64)
|
||||
import Simplex.Chat.Badges.Code (maxNameLength, minNameLength)
|
||||
import Simplex.Messaging.Eth.Address (Address)
|
||||
import Simplex.Messaging.SimplexName (LabelHash (..), SimplexTLD)
|
||||
import Simplex.Messaging.Util (tshow)
|
||||
@@ -135,19 +132,6 @@ chainPriceCents ct len years = call ct (controller ct) ("rentPriceUSD(string,uin
|
||||
where
|
||||
args = abiTuple [Dynamic $ abiBytes $ B.replicate len 'a', Static $ uint256 $ toInteger years * secondsPerYear]
|
||||
|
||||
-- | The shortest length from the shortest label such that every length up to the longest costs at most these cents
|
||||
-- for these years, from one read of the oracle's whole price list, which need not fall with the length.
|
||||
codeLengthFor :: Contracts -> Int -> Int -> IO (Either ChainError (Maybe Int))
|
||||
codeLengthFor ct cents years = runExceptT $ do
|
||||
oracle <- ExceptT $ call ct (controller ct) ("prices()", "") decodeAddress
|
||||
(basePerYear, exceptions) <- ExceptT $ call ct oracle ("prices()", "") decodePrices
|
||||
let exceptionPrices = M.fromList exceptions
|
||||
perYear len = M.findWithDefault basePerYear (toInteger len) exceptionPrices
|
||||
covered len = perYear len * toInteger years <= toInteger cents
|
||||
pure $ case takeWhile covered [maxNameLength, maxNameLength - 1 .. minNameLength] of
|
||||
[] -> Nothing
|
||||
lens -> Just $ last lens
|
||||
|
||||
sponsorNonce :: Contracts -> Address -> IO (Either ChainError Word64)
|
||||
sponsorNonce ct owner = call ct (cSponsor $ chainConfig ct) ("nonces(address)", addressWord owner) decodeWord64
|
||||
|
||||
|
||||
@@ -31,16 +31,12 @@ module StoreService.Names.Orders
|
||||
checkNameFree,
|
||||
checkPrice,
|
||||
movable,
|
||||
issuable,
|
||||
orderFieldsOf,
|
||||
checkMoveMeets,
|
||||
checkMovePrice,
|
||||
retargetOrder,
|
||||
versionWritten,
|
||||
confirmLanded,
|
||||
issuedCodeHash,
|
||||
checkTakenCodeLength,
|
||||
issueTakenCode,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -60,7 +56,6 @@ import Data.Time.Clock (UTCTime, getCurrentTime)
|
||||
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
|
||||
import Data.Word (Word64)
|
||||
import Simplex.Chat.Badges.Code (maxNameLength, minNameLength)
|
||||
import Simplex.Chat.Badges.Types (BadgeCodePaymentStatus (..))
|
||||
import Simplex.Messaging.Agent.Store.Common (DBStore, withTransaction)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Crypto.Secp256k1 (RecoverableSignature)
|
||||
@@ -70,12 +65,12 @@ import Simplex.Messaging.Util (tshow)
|
||||
import StoreService.Config (NamesConfig (..))
|
||||
import StoreService.Log (logError, logWarn)
|
||||
import StoreService.Names.Codes (CodeRefusal (..), codeRefusal, reserveCode, reserveRefusal, spendLandedCode)
|
||||
import StoreService.Names.Contracts (Contracts, NameStatus (..), chainPriceCents, codeLengthFor, confirmedTag, nameExpires, nameStatus, ownerOfToken, secondsPerYear)
|
||||
import StoreService.Names.Contracts (Contracts, NameStatus (..), chainPriceCents, confirmedTag, nameExpires, nameStatus, ownerOfToken, secondsPerYear)
|
||||
import StoreService.Names.Env (NamesEnv (..))
|
||||
import StoreService.Names.Phases (Phases, RegistrationId (..))
|
||||
import StoreService.Names.Abi (bytes32FromHex, hex)
|
||||
import StoreService.Names.Signatures (OrderFields (..), orderMessage, parseChecksumAddress, parseSignature, personalDigest)
|
||||
import StoreService.Names.Store (CodeHashTaken, LiveOrder (..), NamePhase (..), NameRegistration (..), NewPrepaidCode (..), NewRegistration (..), PrepaidCode (..), RegistrationPayment (..), RegistrationRefused (..), NameTransaction (..), TxFor (..), TxStatus (..), VersionWrite (..), closeOverdue, createCodeRegistration, getLiveOrders, getPrepaidCode, getPrepaidCodeById, getRegistrationByOrderHash, insertPrepaidCode, isLabelRegistering, registrationTransactions, retargetRegistration, setIssuedCode, updateRegistration)
|
||||
import StoreService.Names.Store (LiveOrder (..), NamePhase (..), NameRegistration (..), NewRegistration (..), PrepaidCode (..), RegistrationPayment (..), RegistrationRefused (..), NameTransaction (..), TxFor (..), TxStatus (..), VersionWrite (..), closeOverdue, createCodeRegistration, getLiveOrders, getPrepaidCode, getPrepaidCodeById, getRegistrationByOrderHash, isLabelRegistering, registrationTransactions, retargetRegistration, updateRegistration)
|
||||
import StoreService.Names.Turns (Turns, withCodeTurn)
|
||||
|
||||
data OrdersEnv = OrdersEnv
|
||||
@@ -108,10 +103,6 @@ data OrderRefusal
|
||||
| ORCatalogChanged Int
|
||||
| ORCode CodeRefusal
|
||||
| ORPriceTooHigh Int
|
||||
| ORCodeLengthChanged Int
|
||||
| ORCodeUnavailable
|
||||
| ORCodeConflict
|
||||
| ORProviderUnavailable
|
||||
deriving (Eq, Show)
|
||||
|
||||
newtype OrderRefused = OrderRefused OrderRefusal
|
||||
@@ -134,11 +125,6 @@ data WriteRefusal = WRChanged | WRRefused OrderRefusal
|
||||
data Landing = LNotLanded | LRegistered Int64 | LChanged | LUnread
|
||||
deriving (Eq, Show)
|
||||
|
||||
newtype TakenCodeConflict = TakenCodeConflict CodeHashTaken
|
||||
deriving (Show)
|
||||
|
||||
instance E.Exception TakenCodeConflict
|
||||
|
||||
maxSealedChars :: Int
|
||||
maxSealedChars = 11095
|
||||
|
||||
@@ -285,12 +271,6 @@ movable NameRegistration {nrPhase, nrPayment, nrLinksRefusedAt} = case nrPhase o
|
||||
PCommitted -> isJust nrLinksRefusedAt
|
||||
_ -> False
|
||||
|
||||
-- | Only a paid order that lost its name turns into a code.
|
||||
issuable :: NameRegistration -> Bool
|
||||
issuable NameRegistration {nrPhase, nrPayment} = case (nrPhase, nrPayment) of
|
||||
(PTaken, RPInvoice {}) -> True
|
||||
_ -> False
|
||||
|
||||
orderFieldsOf :: NameRegistration -> OrderFields
|
||||
orderFieldsOf NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment, nrSealed} =
|
||||
OrderFields {ofLabelHash = nrLabelHash, ofLength = nrLength, ofDuration = nrDuration, ofCommitment = nrCommitment, ofSealed = nrSealed}
|
||||
@@ -372,35 +352,3 @@ confirmLanded OrdersEnv {oeStore, oeContracts, oeTurns, oeNow} r@NameRegistratio
|
||||
<> ", which was released and then spent, revoked or expired: the name was paid with it"
|
||||
pure v
|
||||
ridText (RegistrationId t) = t
|
||||
|
||||
-- | The hash of the code a taken order was turned into.
|
||||
issuedCodeHash :: DBStore -> NameRegistration -> IO (Maybe ByteString)
|
||||
issuedCodeHash st NameRegistration {nrIssuedCodeId} = case nrIssuedCodeId of
|
||||
Just codeId -> fmap pcCodeHash <$> withTransaction st (`getPrepaidCodeById` codeId)
|
||||
Nothing -> pure Nothing
|
||||
|
||||
-- | The code's minLength for the order's price and years must be the length the page derived the code with.
|
||||
checkTakenCodeLength :: Contracts -> NameRegistration -> Int -> IO (Either OrderRefusal ())
|
||||
checkTakenCodeLength ct NameRegistration {nrPrice, nrYears} sent =
|
||||
codeLengthFor ct nrPrice nrYears >>= \case
|
||||
Left e -> Left ORProviderUnavailable <$ logWarn ("names: a taken code's length read failed: " <> tshow e)
|
||||
Right Nothing -> pure $ Left ORCodeUnavailable
|
||||
Right (Just len)
|
||||
| len /= sent -> pure $ Left $ ORCodeLengthChanged len
|
||||
| otherwise -> pure $ Right ()
|
||||
|
||||
-- | The order becomes code_issued by a compare-and-set on the version read, then its code is inserted and linked, in one
|
||||
-- transaction, so the registration's row is locked before the code's. A hash another code has rolls the order's write back.
|
||||
issueTakenCode :: OrdersEnv -> NameRegistration -> ByteString -> Int -> IO (Either WriteRefusal Int64)
|
||||
issueTakenCode OrdersEnv {oeStore, oeNow} r@NameRegistration {nrId, nrYears} codeHash minLength = do
|
||||
now <- oeNow
|
||||
let code = NewPrepaidCode {npcCodeHash = codeHash, npcMinLength = minLength, npcYears = nrYears, npcPaymentStatus = CPSPaid, npcExpiresAt = Nothing}
|
||||
issue db =
|
||||
updateRegistration db r {nrPhase = PCodeIssued} now >>= \case
|
||||
Written v ->
|
||||
insertPrepaidCode db code now >>= \case
|
||||
Right codeId -> Right v <$ setIssuedCode db nrId codeId
|
||||
Left taken -> E.throwIO $ TakenCodeConflict taken
|
||||
-- a code_issued order is not live, so only a changed version refuses the write
|
||||
_ -> pure $ Left WRChanged
|
||||
either (\(TakenCodeConflict _) -> Left $ WRRefused ORCodeConflict) id <$> E.try (withTransaction oeStore issue)
|
||||
@@ -10,7 +10,6 @@ module StoreService.Names.Signatures
|
||||
orderMessage,
|
||||
findMessage,
|
||||
retargetMessage,
|
||||
takenCodeMessage,
|
||||
parseChecksumAddress,
|
||||
parseAnyCaseAddress,
|
||||
)
|
||||
@@ -100,9 +99,6 @@ findMessage owner timestamp = message ["find", hex $ unAddress owner, tshow time
|
||||
retargetMessage :: RegistrationId -> OrderFields -> Word64 -> ByteString
|
||||
retargetMessage (RegistrationId rid) order timestamp = message $ ["name", rid] <> orderWords order <> [tshow timestamp]
|
||||
|
||||
takenCodeMessage :: RegistrationId -> Text -> Int -> Word64 -> ByteString
|
||||
takenCodeMessage (RegistrationId rid) codeHash codeLength timestamp = message ["code", rid, codeHash, tshow codeLength, tshow timestamp]
|
||||
|
||||
message :: [Text] -> ByteString
|
||||
message = encodeUtf8 . T.unwords . ("simplex.domains" :)
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ module StoreService.Names.Store
|
||||
closeOverdue,
|
||||
updateRegistration,
|
||||
retargetRegistration,
|
||||
setIssuedCode,
|
||||
findRegistrations,
|
||||
isLabelRegistering,
|
||||
activeRegistrations,
|
||||
@@ -121,7 +120,6 @@ data NamePhase
|
||||
| PRegistered
|
||||
| PTaken
|
||||
| PFailed
|
||||
| PCodeIssued
|
||||
deriving (Eq, Show, Enum, Bounded)
|
||||
|
||||
instance TextEncoding NamePhase where
|
||||
@@ -134,7 +132,6 @@ instance TextEncoding NamePhase where
|
||||
PRegistered -> "registered"
|
||||
PTaken -> "taken"
|
||||
PFailed -> "failed"
|
||||
PCodeIssued -> "code_issued"
|
||||
textDecode t = lookup t [(textEncode p, p) | p <- [minBound .. maxBound]]
|
||||
|
||||
instance FromField NamePhase where fromField = fromTextField_ textDecode
|
||||
@@ -179,7 +176,6 @@ data NameRegistration = NameRegistration
|
||||
nrRevealAfter :: Maybe Word64,
|
||||
nrRevealBefore :: Maybe Word64,
|
||||
nrExpiresAt :: Maybe Word64,
|
||||
nrIssuedCodeId :: Maybe Int64,
|
||||
nrRegisteredAt :: Maybe UTCTime,
|
||||
nrFailedAt :: Maybe UTCTime,
|
||||
nrError :: Maybe Text,
|
||||
@@ -401,7 +397,7 @@ registrationColumns =
|
||||
[sql|
|
||||
SELECT r.registration_id, r.version, r.label_hash, r.length, r.duration, r.owner, r.commitment, r.sealed, r.phase, r.years,
|
||||
r.price, r.method, ni.invoice_id, r.name_prepaid_code_id, r.label, r.secret, r.contact_link, r.channel_link, r.intent_nonce, r.intent_deadline,
|
||||
r.intent_signature, r.commit_tx, r.commit_block, r.register_tx, r.register_block, r.reveal_after, r.reveal_before, r.expires_at, r.issued_code_id, r.registered_at,
|
||||
r.intent_signature, r.commit_tx, r.commit_block, r.register_tx, r.register_block, r.reveal_after, r.reveal_before, r.expires_at, r.registered_at,
|
||||
r.failed_at, r.error, r.live, r.order_hash, r.moves, r.links_refused_at, r.created_at, r.updated_at
|
||||
|]
|
||||
|
||||
@@ -419,14 +415,14 @@ selectRegistrations = registrationColumns <> registrationFrom
|
||||
type RegistrationRow =
|
||||
(Text, Int64, Binary ByteString, Int, Int64, Binary ByteString, Binary ByteString, Text, NamePhase, Int)
|
||||
:. (Int, Maybe Text, Maybe Text, Maybe Int64, Maybe Text, Maybe (Binary ByteString), Maybe Text, Maybe Text, Maybe Int64, Maybe Int64)
|
||||
:. (Maybe (Binary ByteString), Maybe (Binary ByteString), Maybe Int64, Maybe (Binary ByteString), Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime)
|
||||
:. (Maybe (Binary ByteString), Maybe (Binary ByteString), Maybe Int64, Maybe (Binary ByteString), Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime)
|
||||
:. (Maybe UTCTime, Maybe Text, BoolInt, Binary ByteString, Int, Maybe UTCTime, UTCTime, UTCTime)
|
||||
|
||||
toRegistration :: RegistrationRow -> Either Text NameRegistration
|
||||
toRegistration
|
||||
( (rid, nrVersion, Binary lh, nrLength, duration, Binary owner, Binary nrCommitment, nrSealed, nrPhase, nrYears)
|
||||
:. (nrPrice, method_, invoiceId_, codeId_, label_, secret_, nrContactLink, nrChannelLink, nonce_, deadline_)
|
||||
:. (signature_, commitTx, commitBlock, registerTx, registerBlock, revealAfter, revealBefore, expiresAt, nrIssuedCodeId, nrRegisteredAt)
|
||||
:. (signature_, commitTx, commitBlock, registerTx, registerBlock, revealAfter, revealBefore, expiresAt, nrRegisteredAt)
|
||||
:. (nrFailedAt, nrError, BI nrLive, Binary nrOrderHash, nrMoves, nrLinksRefusedAt, nrCreatedAt, nrUpdatedAt)
|
||||
) = do
|
||||
nrOwner <- decodeAddress owner
|
||||
@@ -462,7 +458,6 @@ toRegistration
|
||||
nrRevealAfter = word64 <$> revealAfter,
|
||||
nrRevealBefore = word64 <$> revealBefore,
|
||||
nrExpiresAt = word64 <$> expiresAt,
|
||||
nrIssuedCodeId,
|
||||
nrRegisteredAt,
|
||||
nrFailedAt,
|
||||
nrError,
|
||||
@@ -552,7 +547,6 @@ orderLive NameRegistration {nrPhase, nrPayment, nrLive} = case nrPhase of
|
||||
RPInvoice {} -> True
|
||||
RPCode _ -> False
|
||||
PRegistered -> False
|
||||
PCodeIssued -> False
|
||||
|
||||
-- | Writes every changeable field of a registration read at its nrVersion, unless it changed since, with live as orderLive decides.
|
||||
-- A closed order is never made live again, and a write that would make the order meet another live one changes nothing.
|
||||
@@ -566,7 +560,7 @@ updateRegistration db r now = do
|
||||
SET version = version + 1, label_hash = ?, length = ?, duration = ?, commitment = ?, sealed = ?, phase = ?, price = ?,
|
||||
label = ?, secret = ?, contact_link = ?, channel_link = ?, intent_nonce = ?, intent_deadline = ?, intent_signature = ?,
|
||||
commit_tx = ?, commit_block = ?, register_tx = ?, register_block = ?, reveal_after = ?, reveal_before = ?, expires_at = ?,
|
||||
issued_code_id = ?, registered_at = ?, failed_at = ?, error = ?, live = ?, moves = ?, links_refused_at = ?, updated_at = ?
|
||||
registered_at = ?, failed_at = ?, error = ?, live = ?, moves = ?, links_refused_at = ?, updated_at = ?
|
||||
WHERE registration_id = ? AND version = ?
|
||||
AND (? = 0 OR (live = 1 AND NOT EXISTS (
|
||||
SELECT 1 FROM sx_badge_service_name_registrations o
|
||||
@@ -576,14 +570,14 @@ updateRegistration db r now = do
|
||||
( (Binary lh, nrLength, int64 nrDuration, Binary nrCommitment, nrSealed, nrPhase, nrPrice)
|
||||
:. (rvLabel <$> nrReveal, Binary . rvSecret <$> nrReveal, rvContactLink =<< nrReveal, rvChannelLink =<< nrReveal, int64 . rvNonce <$> nrReveal, int64 . rvDeadline <$> nrReveal, Binary . serializeSignature . rvSignature <$> nrReveal)
|
||||
:. (txHashField <$> nrCommitTx, int64 <$> nrCommitBlock, txHashField <$> nrRegisterTx, int64 <$> nrRegisterBlock, int64 <$> nrRevealAfter, int64 <$> nrRevealBefore, int64 <$> nrExpiresAt)
|
||||
:. (nrIssuedCodeId, truncateToSecond <$> nrRegisteredAt, truncateToSecond <$> nrFailedAt, nrError, BI live, nrMoves, truncateToSecond <$> nrLinksRefusedAt, truncateToSecond now)
|
||||
:. (truncateToSecond <$> nrRegisteredAt, truncateToSecond <$> nrFailedAt, nrError, BI live, nrMoves, truncateToSecond <$> nrLinksRefusedAt, truncateToSecond now)
|
||||
:. (rid, nrVersion, BI live, rid, Binary nrCommitment, Binary $ unAddress nrOwner, Binary lh)
|
||||
)
|
||||
if changed > 0 then pure $ Written (nrVersion + 1) else refusal
|
||||
where
|
||||
NameRegistration {nrId = RegistrationId rid, nrVersion, nrLabelHash = LabelHash lh, nrLength, nrDuration, nrOwner, nrCommitment, nrSealed, nrPhase, nrPrice, nrReveal} = r
|
||||
NameRegistration {nrCommitTx, nrCommitBlock, nrRegisterTx, nrRegisterBlock, nrRevealAfter, nrRevealBefore, nrExpiresAt} = r
|
||||
NameRegistration {nrIssuedCodeId, nrRegisteredAt, nrFailedAt, nrError, nrMoves, nrLinksRefusedAt} = r
|
||||
NameRegistration {nrRegisteredAt, nrFailedAt, nrError, nrMoves, nrLinksRefusedAt} = r
|
||||
live = orderLive r
|
||||
refusal =
|
||||
DB.query db "SELECT version, live FROM sx_badge_service_name_registrations WHERE registration_id = ?" (Only rid) >>= \case
|
||||
@@ -617,10 +611,6 @@ retargetRegistration db r OrderFields {ofLabelHash, ofLength, ofDuration, ofComm
|
||||
nrError = Nothing
|
||||
}
|
||||
|
||||
-- | Links the code a taken order was turned into, in the transaction that wrote its code_issued phase and version.
|
||||
setIssuedCode :: DB.Connection -> RegistrationId -> Int64 -> IO ()
|
||||
setIssuedCode db (RegistrationId rid) codeId = DB.execute db "UPDATE sx_badge_service_name_registrations SET issued_code_id = ? WHERE registration_id = ?" (codeId, rid)
|
||||
|
||||
-- | Newest first, at most findLimit.
|
||||
findRegistrations :: DB.Connection -> Address -> IO [NameRegistration]
|
||||
findRegistrations db owner =
|
||||
|
||||
@@ -33,39 +33,32 @@ import Simplex.Messaging.Util (safeDecodeUtf8)
|
||||
import StoreService.Config (ChainConfig (..), NamesConfig (..))
|
||||
import StoreService.Names.Abi (hex)
|
||||
import StoreService.Names.Chain (TxHash (..))
|
||||
import StoreService.Names.Contracts (Contracts, codeLengthFor, sponsorNonce)
|
||||
import StoreService.Names.Contracts (Contracts, sponsorNonce)
|
||||
import StoreService.Names.Phases (RegistrationId (..))
|
||||
import StoreService.Names.Store (NamePhase (..), NameRegistration (..), NameReveal (..), PrepaidCode (..), RegistrationPayment (..), getPrepaidCodeById, getRegistration)
|
||||
import StoreService.Names.Store (NamePhase (..), NameRegistration (..), NameReveal (..), getRegistration)
|
||||
|
||||
-- | The view's chain reads; a field whose read failed is null and named in vrUnread.
|
||||
data ViewReads = ViewReads
|
||||
{ vrIntentNonce :: Maybe Word64,
|
||||
vrCodeLength :: Maybe Int,
|
||||
vrUnread :: [Text]
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
registrationView :: DBStore -> NamesConfig -> Contracts -> NameRegistration -> IO J.Value
|
||||
registrationView st cfg ct r = viewJSON cfg r <$> readViewChain st ct r
|
||||
registrationView _ cfg ct r = viewJSON cfg r <$> readViewChain ct r
|
||||
|
||||
-- | The intent nonce only in committed; a code length for a paid order in taken, and the issued code's in code_issued.
|
||||
readViewChain :: DBStore -> Contracts -> NameRegistration -> IO ViewReads
|
||||
readViewChain st ct NameRegistration {nrPhase, nrOwner, nrPayment, nrPrice, nrYears, nrIssuedCodeId} = case nrPhase of
|
||||
-- | The intent nonce only in committed.
|
||||
readViewChain :: Contracts -> NameRegistration -> IO ViewReads
|
||||
readViewChain ct NameRegistration {nrPhase, nrOwner} = case nrPhase of
|
||||
PCommitted -> sponsorNonce ct nrOwner >>= \case
|
||||
Right nonce -> pure none {vrIntentNonce = Just nonce}
|
||||
Left _ -> pure none {vrUnread = ["intentNonce"]}
|
||||
PTaken | RPInvoice {} <- nrPayment -> codeLengthFor ct nrPrice nrYears >>= \case
|
||||
Right len -> pure none {vrCodeLength = len}
|
||||
Left _ -> pure none {vrUnread = ["codeLength"]}
|
||||
PCodeIssued -> do
|
||||
code <- maybe (pure Nothing) (\codeId -> withTransaction st (`getPrepaidCodeById` codeId)) nrIssuedCodeId
|
||||
pure none {vrCodeLength = pcMinLength <$> code}
|
||||
_ -> pure none
|
||||
where
|
||||
none = ViewReads {vrIntentNonce = Nothing, vrCodeLength = Nothing, vrUnread = []}
|
||||
none = ViewReads {vrIntentNonce = Nothing, vrUnread = []}
|
||||
|
||||
viewJSON :: NamesConfig -> NameRegistration -> ViewReads -> J.Value
|
||||
viewJSON NamesConfig {nTLD, nChain = ChainConfig {cChainId, cResolver, cSponsor}} r ViewReads {vrIntentNonce, vrCodeLength, vrUnread} =
|
||||
viewJSON NamesConfig {nTLD, nChain = ChainConfig {cChainId, cResolver, cSponsor}} r ViewReads {vrIntentNonce, vrUnread} =
|
||||
J.object
|
||||
[ "id" .= rid,
|
||||
"version" .= nrVersion,
|
||||
@@ -84,7 +77,6 @@ viewJSON NamesConfig {nTLD, nChain = ChainConfig {cChainId, cResolver, cSponsor}
|
||||
"years" .= nrYears,
|
||||
"duration" .= nrDuration,
|
||||
"price" .= nrPrice,
|
||||
"codeLength" .= vrCodeLength,
|
||||
"commitTx" .= (txText <$> nrCommitTx),
|
||||
"registerTx" .= (txText <$> nrRegisterTx),
|
||||
"revealAfter" .= nrRevealAfter,
|
||||
|
||||
@@ -12,7 +12,7 @@ import StoreService.Names.Orders (ordersEnv)
|
||||
import StoreService.Web.Http (HttpEnv, Limit, Respond, codeLimit, createLimit, findLimit, holdMicros, limited, only, previewLimit, readLimit, recordsLimit, searchLimit)
|
||||
import StoreService.Web.Names.Codes (codeCheckHandler)
|
||||
import StoreService.Web.Names.Orders (codeOrderHandler)
|
||||
import StoreService.Web.Names.Owner (findHandler, retargetHandler, takenCodeHandler)
|
||||
import StoreService.Web.Names.Owner (findHandler, retargetHandler)
|
||||
import StoreService.Web.Names.Records (recordsHandler)
|
||||
import StoreService.Web.Names.Registration (registrationHandler, revealHandler)
|
||||
import StoreService.Web.Names.Search (previewHandler, searchHandler)
|
||||
@@ -35,7 +35,6 @@ namesRoutes env@NamesEnv {neConfig, neStore, neContracts, neProfiles, neRecords,
|
||||
["api", "registration", "find"] -> Just Route {rMethod = "POST", rLimit = findLimit, rHandler = findHandler (ordersEnv env)}
|
||||
["api", "registration", rid] -> Just Route {rMethod = "GET", rLimit = readLimit, rHandler = registrationHandler (ordersEnv env) holdMicros rid}
|
||||
["api", "registration", rid, "name"] -> Just Route {rMethod = "POST", rLimit = createLimit, rHandler = retargetHandler (ordersEnv env) rid}
|
||||
["api", "registration", rid, "code"] -> Just Route {rMethod = "POST", rLimit = createLimit, rHandler = takenCodeHandler (ordersEnv env) rid}
|
||||
["api", "registration", rid, "reveal"] -> Just Route {rMethod = "POST", rLimit = readLimit, rHandler = revealHandler (ordersEnv env) rid}
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ import StoreService.Orders (publishRegistration)
|
||||
import StoreService.Providers (OrderDraft (..), Provider (..), ProviderError (..), ProviderInvoice (..), expiresItself)
|
||||
import StoreService.Store.Invoices (InvoiceRow (..), NewNameInvoice (..), getInvoice, insertNameInvoice, newInvoiceId, truncateToSecond)
|
||||
import StoreService.Waiters (Waiters, publish)
|
||||
import StoreService.Web.Http (Respond, badRequest, codeConflict, confirmationsFor, internalError, invalidName, invoiceAt, invoicePairs, invoiceWindow, jsonError, jsonResponse, nameBodyBytes, providerFor, providerNamed, providerUnavailable, readBoundedBody)
|
||||
import StoreService.Web.Http (Respond, badRequest, confirmationsFor, internalError, invalidName, invoiceAt, invoicePairs, invoiceWindow, jsonError, jsonResponse, nameBodyBytes, providerFor, providerNamed, providerUnavailable, readBoundedBody)
|
||||
import StoreService.Web.Names.Codes (codeRefusalResponse)
|
||||
|
||||
data InvoiceOrder = InvoiceOrder
|
||||
@@ -236,7 +236,3 @@ orderRefusalResponse = \case
|
||||
ORCatalogChanged price -> jsonResponse status409 $ J.object ["error" .= ("catalog_changed" :: Text), "price" .= price]
|
||||
ORCode refusal -> codeRefusalResponse refusal
|
||||
ORPriceTooHigh price -> jsonResponse status409 $ J.object ["error" .= ("price_too_high" :: Text), "price" .= price]
|
||||
ORCodeLengthChanged len -> jsonResponse status409 $ J.object ["error" .= ("code_length_changed" :: Text), "codeLength" .= len]
|
||||
ORCodeUnavailable -> jsonError status409 "code_unavailable"
|
||||
ORCodeConflict -> codeConflict
|
||||
ORProviderUnavailable -> providerUnavailable
|
||||
@@ -2,14 +2,13 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
-- | The requests only the owner code can sign: find, another name, and a code for a taken order.
|
||||
module StoreService.Web.Names.Owner (findHandler, retargetHandler, takenCodeHandler) where
|
||||
-- | The requests only the owner code can sign: find and another name.
|
||||
module StoreService.Web.Names.Owner (findHandler, retargetHandler) where
|
||||
|
||||
import Control.Concurrent.STM (atomically)
|
||||
import Data.Aeson ((.=))
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.KeyMap as KM
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.Int (Int64)
|
||||
import Data.Text (Text)
|
||||
import Data.Word (Word64)
|
||||
@@ -27,11 +26,11 @@ import StoreService.Log (logError, logInfo)
|
||||
import StoreService.Names.Abi (hex)
|
||||
import StoreService.Names.Orders
|
||||
import StoreService.Names.Phases (RegistrationId (..), publish)
|
||||
import StoreService.Names.Signatures (OrderFields (..), findMessage, parseAnyCaseAddress, parseSignature, retargetMessage, takenCodeMessage)
|
||||
import StoreService.Names.Signatures (OrderFields (..), findMessage, parseAnyCaseAddress, parseSignature, retargetMessage)
|
||||
import StoreService.Names.Store (NamePhase (..), NameRegistration (..), RegistrationPayment (..), findRegistrations, getRegistration)
|
||||
import StoreService.Names.Turns (withLabelTurn, withLabelTurns)
|
||||
import StoreService.Names.Turns (withLabelTurns)
|
||||
import StoreService.Names.View (registrationAt, registrationView)
|
||||
import StoreService.Web.Http (Respond, internalError, jsonResponse, maxBodyBytes, nameBodyBytes, notFound, parseCodeHash, readBoundedBody)
|
||||
import StoreService.Web.Http (Respond, internalError, jsonResponse, maxBodyBytes, nameBodyBytes, notFound, readBoundedBody)
|
||||
import StoreService.Web.Names.Orders (Answer (..), answerNow, firstRefusal, orderRefusalResponse, sendAnswer)
|
||||
import StoreService.Web.Names.Registration (registrationChangedResponse, registrationRefusalResponse)
|
||||
|
||||
@@ -41,14 +40,6 @@ data FindRequest = FindRequest
|
||||
frSignature :: RecoverableSignature
|
||||
}
|
||||
|
||||
data TakenCodeRequest = TakenCodeRequest
|
||||
{ tcHashText :: Text,
|
||||
tcHash :: ByteString,
|
||||
tcLength :: Int,
|
||||
tcTimestamp :: Word64,
|
||||
tcSignature :: RecoverableSignature
|
||||
}
|
||||
|
||||
-- | POST /api/registration/find, the owner's orders newest first.
|
||||
findHandler :: OrdersEnv -> Request -> Respond -> IO ResponseReceived
|
||||
findHandler OrdersEnv {oeStore, oeNow} req respond =
|
||||
@@ -144,56 +135,6 @@ retargetHandler env@OrdersEnv {oeStore, oeConfig, oeContracts, oeTurns, oeNow} r
|
||||
changed = answerNow <$> registrationChangedResponse env rid
|
||||
refuse = refused "POST /api/registration/:id/name"
|
||||
|
||||
-- | POST /api/registration/:id/code, a paid taken order turned into a prepaid code, under its name's turn.
|
||||
takenCodeHandler :: OrdersEnv -> Text -> Request -> Respond -> IO ResponseReceived
|
||||
takenCodeHandler env@OrdersEnv {oeStore, oeContracts, oeTurns, oeNow} ridText req respond =
|
||||
readBoundedBody maxBodyBytes req >>= requested >>= sendAnswer respond
|
||||
where
|
||||
rid = RegistrationId ridText
|
||||
requested = \case
|
||||
Just body | Just (J.Object o) <- J.decode body, Just request <- parseTakenCode o -> signed request
|
||||
_ -> refuse ORBadRequest
|
||||
signed request@TakenCodeRequest {tcHashText, tcLength, tcTimestamp, tcSignature} =
|
||||
withTransaction oeStore (`getRegistration` rid) >>= \case
|
||||
Nothing -> pure $ answerNow notFound
|
||||
Just NameRegistration {nrOwner, nrLabelHash} ->
|
||||
checkSigner nrOwner (takenCodeMessage rid tcHashText tcLength tcTimestamp) tcSignature >>= \case
|
||||
Left refusal -> refuse refusal
|
||||
Right () -> withLabelTurn oeTurns nrLabelHash $ inTurn nrLabelHash request
|
||||
inTurn held request@TakenCodeRequest {tcHash} = do
|
||||
now <- oeNow
|
||||
withTransaction oeStore (`getRegistration` rid) >>= \case
|
||||
Nothing -> pure $ answerNow notFound
|
||||
Just r
|
||||
| nrLabelHash r /= held -> changed
|
||||
| otherwise ->
|
||||
issuedCodeHash oeStore r >>= \case
|
||||
Just issued | issued == tcHash -> pure $ answerNow codeIssued
|
||||
_ -> issue now r request
|
||||
issue now r TakenCodeRequest {tcHash, tcLength, tcTimestamp}
|
||||
| Left refusal <- checkTimestamp now tcTimestamp = refuse refusal
|
||||
| not (issuable r) = refuse ORBadRequest
|
||||
| otherwise =
|
||||
checkTakenCodeLength oeContracts r tcLength >>= \case
|
||||
Left refusal -> refuse refusal
|
||||
Right () ->
|
||||
issueTakenCode env r tcHash tcLength >>= \case
|
||||
Right v -> pure $ written env rid v codeIssued
|
||||
Left WRChanged -> changed
|
||||
Left (WRRefused refusal) -> refuse refusal
|
||||
codeIssued = jsonResponse status200 $ J.object []
|
||||
changed = answerNow <$> registrationChangedResponse env rid
|
||||
refuse = refused "POST /api/registration/:id/code"
|
||||
|
||||
parseTakenCode :: J.Object -> Maybe TakenCodeRequest
|
||||
parseTakenCode o = do
|
||||
tcHashText <- textField o "codeHash"
|
||||
tcHash <- parseCodeHash tcHashText
|
||||
tcLength <- integerField =<< KM.lookup "codeLength" o
|
||||
tcTimestamp <- integerField =<< KM.lookup "timestamp" o
|
||||
tcSignature <- eitherToMaybe . parseSignature =<< textField o "signature"
|
||||
pure TakenCodeRequest {tcHashText, tcHash, tcLength, tcTimestamp, tcSignature}
|
||||
|
||||
-- | Published after the turn, as the answer is sent.
|
||||
written :: OrdersEnv -> RegistrationId -> Int64 -> Response -> Answer
|
||||
written OrdersEnv {oePhases} rid v aResponse = Answer {aResponse, aAfter = atomically $ publish oePhases rid v}
|
||||
|
||||
+4
-21
@@ -66,8 +66,7 @@ badgeTests = do
|
||||
describe "StoreService names: codes" $ do
|
||||
it "encodes an owner code's entropy and its checksum" testNameOwnerCodeVectors
|
||||
it "rejects an owner code with a wrong character" testNameOwnerCodeChecksum
|
||||
it "reads the webapp's prepaid codes: length, canonical form, display and hash" testNamePrepaidCodeVectors
|
||||
it "reads a taken order's code derived by the webapp, and refuses its lengths outside 6 to 63" testTakenOrderCodeVectors
|
||||
it "reads the webapp's prepaid codes: length, canonical form, display and hash, and refuses lengths outside 6 to 63" testNamePrepaidCodeVectors
|
||||
it "never reads an owner code as prepaid, nor the reverse" testNameCodeKinds
|
||||
it "reads random codes of both kinds back, as typed" testNameCodeRoundTrip
|
||||
it "refuses a prepaid code with one character changed or two adjacent swapped, except 0 and Z" testNamePrepaidCodeCheck
|
||||
@@ -374,6 +373,9 @@ testNamePrepaidCodeVectors = do
|
||||
convertToBase Base64URLUnpadded (namePrepaidCodeHash code) `shouldBe` (hash :: ByteString)
|
||||
-- the webapp's typo vector
|
||||
parseNameCode "SN7-4K2P7-TQ9M1-ZX3RB-8HJ5X" `shouldBe` Nothing
|
||||
-- their check characters are valid, but no name length is outside 6 to 63
|
||||
parseNameCode "SN5-N0N9J-5D292-HPSYA-R911Q" `shouldBe` Nothing
|
||||
parseNameCode "SN64-N0N9J-5D292-HPSYA-R911C" `shouldBe` Nothing
|
||||
|
||||
-- typed code, length, canonical form, display and the webapp's codeHash
|
||||
namePrepaidCodeVectors :: [(T.Text, Int, T.Text, T.Text, ByteString)]
|
||||
@@ -383,25 +385,6 @@ namePrepaidCodeVectors =
|
||||
("SN8-25X6F-6JDNM-8C59E-HDHJD", 8, "SN825X6F6JDNM8C59EHDHJD", "SN8-25X6F-6JDNM-8C59E-HDHJD", "cOnJDSzWBEZiY2z1I-HMGW2gylyx_13V8sOpMDeJOvU")
|
||||
]
|
||||
|
||||
-- takenOrderCode(seed of the webapp's fixture entropy, "t2Jk8RmQ4vXw9NcP3yLb6g", length), computed by the webapp's code.ts
|
||||
testTakenOrderCodeVectors :: IO ()
|
||||
testTakenOrderCodeVectors = do
|
||||
forM_ takenOrderCodeVectors $ \(display, len, hash) -> do
|
||||
Just (NCPrepaid code) <- pure $ parseNameCode display
|
||||
(namePrepaidCodeLength code, formatNameCode (NCPrepaid code)) `shouldBe` (len, display)
|
||||
convertToBase Base64URLUnpadded (namePrepaidCodeHash code) `shouldBe` (hash :: ByteString)
|
||||
-- the webapp reads these, but no name length is outside 6 to 63
|
||||
parseNameCode "SN5-N0N9J-5D292-HPSYA-R911Q" `shouldBe` Nothing
|
||||
parseNameCode "SN64-N0N9J-5D292-HPSYA-R911C" `shouldBe` Nothing
|
||||
|
||||
takenOrderCodeVectors :: [(T.Text, Int, ByteString)]
|
||||
takenOrderCodeVectors =
|
||||
[ ("SN6-N0N9J-5D292-HPSYA-R911P", 6, "SKKFjAl5K21B6HRIgQcYQ3AaZ67nVxl2xszsSkZ97cA"),
|
||||
("SN7-N0N9J-5D292-HPSYA-R911N", 7, "pXhWV9oSfjH0oaQ78HPSGKTg1lScZhNGi-YZnqt-GyQ"),
|
||||
("SN12-N0N9J-5D292-HPSYA-R911R", 12, "FozeCQ3A_sUsZF9_OwQDp42kdHRMsoJazuGvOkTS-FQ"),
|
||||
("SN63-N0N9J-5D292-HPSYA-R911D", 63, "U-Nh_Kw3-xDlQuFJffkuCAyolK5m9EfN3QuYiJ28A0A")
|
||||
]
|
||||
|
||||
testNameCodeKinds :: IO ()
|
||||
testNameCodeKinds = do
|
||||
drg <- C.newRandom
|
||||
|
||||
@@ -137,7 +137,7 @@ badgeGroupIntegrationTests = do
|
||||
it "leaves a tracker it cannot edit alone rather than publishing the code again" testUneditableTrackerLeftAlone
|
||||
|
||||
namesBootTests :: SpecWith TestParams
|
||||
namesBootTests = it "boots with [names] and [chain], routing fixed /api/name paths ahead of a search, find ahead of registrations, registrations to GET, another name, a code and a reveal to POST, a path no route knows to 404, and code checks and orders under one limit" testNamesBoot
|
||||
namesBootTests = it "boots with [names] and [chain], routing fixed /api/name paths ahead of a search, find ahead of registrations, registrations to GET, another name and a reveal to POST, a path no route knows and a taken order's code to 404, and code checks and orders under one limit" testNamesBoot
|
||||
|
||||
testNamesBoot :: HasCallStack => TestParams -> IO ()
|
||||
testNamesBoot ps = do
|
||||
@@ -165,9 +165,10 @@ testNamesBoot ps = do
|
||||
answer found {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` refused "bad_request"
|
||||
foundByGet <- httpLbs found mgr
|
||||
(statusCode $ responseStatus foundByGet, lookup "Allow" $ responseHeaders foundByGet) `shouldBe` (405, Just "POST")
|
||||
forM_ ["name", "code"] $ \action -> do
|
||||
owned <- request $ "/api/registration/t2Jk8RmQ4vXw9NcP3yLb6g/" <> action
|
||||
(action,) <$> answer owned {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` (action, refused "bad_request")
|
||||
owned <- request "/api/registration/t2Jk8RmQ4vXw9NcP3yLb6g/name"
|
||||
answer owned {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` refused "bad_request"
|
||||
coded <- request "/api/registration/t2Jk8RmQ4vXw9NcP3yLb6g/code"
|
||||
answer coded {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` (404, Just $ J.object ["error" J..= ("not_found" :: Text)])
|
||||
revealed <- request "/api/registration/t2Jk8RmQ4vXw9NcP3yLb6g/reveal"
|
||||
answer revealed {method = "POST", requestBody = RequestBodyLBS "{}"} `shouldReturn` (404, Just $ J.object ["error" J..= ("not_found" :: Text)])
|
||||
revealedByGet <- httpLbs revealed mgr
|
||||
|
||||
@@ -20,10 +20,6 @@ module Bots.StoreService.NamesContractsTests
|
||||
contactTextCall,
|
||||
channelTextCall,
|
||||
noncesCall,
|
||||
pricesCall,
|
||||
launchPricesAnswer,
|
||||
pricesAnswer,
|
||||
oracle,
|
||||
alice,
|
||||
availableCall,
|
||||
minCommitmentAgeCall,
|
||||
@@ -70,9 +66,6 @@ namesContractsTests = describe "StoreService names: contracts" $ do
|
||||
it "reads no records from another resolver" $ testOtherResolver g
|
||||
describe "prices" $ do
|
||||
it "quotes 6, 7 and 12 letters at the launch list for 2 years" $ testLaunchPrices g
|
||||
it "finds the code length for each launch price" $ testLaunchCodeLengths g
|
||||
it "requires every longer length to be covered when the list is not monotone" $ testNonMonotone g
|
||||
it "reads the oracle from the controller on each call" $ testOracleReplaced g
|
||||
describe "other reads" $ do
|
||||
it "reads the sponsor's and the resolver's nonces from their own contracts" $ testNonces g
|
||||
it "reads ownerOf's revert without data as nobody" $ testOwnerOfNobody g
|
||||
@@ -164,32 +157,6 @@ testLaunchPrices g = withContracts g $ \node ct -> do
|
||||
answerCall node fakeController (rentPriceCall 12) $ Returns $ rentPriceAnswer 10000
|
||||
mapM (\len -> chainPriceCents ct len 2) [6, 7, 12] `shouldReturn` [Right 40000, Right 20000, Right 10000]
|
||||
|
||||
testLaunchCodeLengths :: TVar ChaChaDRG -> IO ()
|
||||
testLaunchCodeLengths g = withContracts g $ \node ct -> do
|
||||
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
|
||||
answerCall node oracle pricesCall $ Returns launchPricesAnswer
|
||||
mapM (\cents -> codeLengthFor ct cents 2) [40000, 20000, 10000, 9999]
|
||||
`shouldReturn` [Right (Just 6), Right (Just 7), Right (Just 8), Right Nothing]
|
||||
codeLengthFor ct 20000 4 `shouldReturn` Right (Just 8)
|
||||
|
||||
testNonMonotone :: TVar ChaChaDRG -> IO ()
|
||||
testNonMonotone g = withContracts g $ \node ct -> do
|
||||
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
|
||||
answerCall node oracle pricesCall $ Returns $ pricesAnswer 5000 [(6, 15000), (7, 20000)]
|
||||
mapM (\cents -> codeLengthFor ct cents 2) [30000, 40000] `shouldReturn` [Right (Just 8), Right (Just 6)]
|
||||
answerCall node oracle pricesCall $ Returns $ pricesAnswer 5000 [(63, 30000)]
|
||||
mapM (\cents -> codeLengthFor ct cents 2) [59999, 60000] `shouldReturn` [Right Nothing, Right (Just 6)]
|
||||
|
||||
testOracleReplaced :: TVar ChaChaDRG -> IO ()
|
||||
testOracleReplaced g = withContracts g $ \node ct -> do
|
||||
let newOracle = address "0x8a791620dd6260079bf849dc5567adc3f2fdc318"
|
||||
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
|
||||
answerCall node oracle pricesCall $ Returns launchPricesAnswer
|
||||
answerCall node newOracle pricesCall $ Returns $ pricesAnswer 10000 []
|
||||
codeLengthFor ct 20000 2 `shouldReturn` Right (Just 7)
|
||||
answerCall node fakeController pricesCall $ Returns $ addressWord newOracle
|
||||
codeLengthFor ct 20000 2 `shouldReturn` Right (Just 6)
|
||||
|
||||
testNonces :: TVar ChaChaDRG -> IO ()
|
||||
testNonces g = withContracts g $ \node ct -> do
|
||||
answerCall node fakeSponsor noncesCall $ Returns $ uint256 (5 :: Int)
|
||||
@@ -265,9 +232,6 @@ testMalformed g = withContracts g $ \node ct -> do
|
||||
nameExpires ct Latest alice `shouldReturn` Left (ChainUnavailable "eth_call: nameExpires(uint256) answered AEShort")
|
||||
answerCall node fakeSponsor noncesCall $ Returns $ uint256 (toInteger (maxBound :: Word64) + 1)
|
||||
sponsorNonce ct owner `shouldReturn` Left (ChainUnavailable "eth_call: nonces(address) answered AERange")
|
||||
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
|
||||
answerCall node oracle pricesCall $ Returns $ B.dropEnd 32 launchPricesAnswer
|
||||
codeLengthFor ct 40000 2 `shouldReturn` Left (ChainUnavailable "eth_call: prices() answered AEShort")
|
||||
|
||||
testStatusDeadline :: TVar ChaChaDRG -> IO ()
|
||||
testStatusDeadline g = withContracts g $ \node ct -> do
|
||||
@@ -347,13 +311,6 @@ resolverOf = cResolver . fakeChainConfig
|
||||
rentPriceAnswer :: Integer -> ByteString
|
||||
rentPriceAnswer cents = uint256 (cents * 10 ^ (16 :: Int)) <> uint256 (0 :: Int)
|
||||
|
||||
pricesAnswer :: Integer -> [(Integer, Integer)] -> ByteString
|
||||
pricesAnswer base exceptions = abiTuple [Static $ uint256 base, Dynamic $ uint256 (length exceptions) <> B.concat [uint256 len <> uint256 cents | (len, cents) <- exceptions]]
|
||||
|
||||
-- viem: prices() answering the launch list, base 5000 and [{7, 10000}, {6, 20000}]
|
||||
launchPricesAnswer :: ByteString
|
||||
launchPricesAnswer = unhex "0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000004e20"
|
||||
|
||||
-- viem: text answering "https://smp8.simplex.im/a#Y8m3"
|
||||
contactTextAnswer :: ByteString
|
||||
contactTextAnswer = unhex "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e68747470733a2f2f736d70382e73696d706c65782e696d2f612359386d330000"
|
||||
@@ -376,10 +333,9 @@ aliceNode = unhex "0f4104919a21c22f566d9c7947c7c7d8addbf546c973851a7e449276ea54e
|
||||
commitment :: ByteString
|
||||
commitment = unhex "f8096c3f3cfbadc9f3a108d2c586ccc816c0510711c8d6228aa4fa507324d30c"
|
||||
|
||||
owner, registry, oracle, otherResolver :: Address
|
||||
owner, registry, otherResolver :: Address
|
||||
owner = address "0x014f9c09cceedef1b713110d04d3a07d5b5d9b73"
|
||||
registry = address "0x5fbdb2315678afecb367f032d93f642f64180aa3"
|
||||
oracle = address "0xa513e6e4b8f2a923d98304ec87f64353c4d5c853"
|
||||
otherResolver = address "0x4826533b4897376654bb4d4ad88b7fafd0c98528"
|
||||
|
||||
-- viem's calldata for the reads
|
||||
@@ -407,8 +363,7 @@ rentPriceCall = \case
|
||||
12 -> unhex "9ff7ec0900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000003c26700000000000000000000000000000000000000000000000000000000000000000c6161616161616161616161610000000000000000000000000000000000000000"
|
||||
len -> error $ "no viem vector for length " <> show len
|
||||
|
||||
pricesCall, commitmentsCall, availableCall, minCommitmentAgeCall, maxCommitmentAgeCall :: ByteString
|
||||
pricesCall = unhex "d3419bf3"
|
||||
commitmentsCall, availableCall, minCommitmentAgeCall, maxCommitmentAgeCall :: ByteString
|
||||
commitmentsCall = unhex "839df945f8096c3f3cfbadc9f3a108d2c586ccc816c0510711c8d6228aa4fa507324d30c"
|
||||
availableCall = unhex "aeb8ce9b0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b616c69636562616b657279000000000000000000000000000000000000000000"
|
||||
minCommitmentAgeCall = unhex "8d839ffe"
|
||||
|
||||
@@ -34,8 +34,8 @@ where
|
||||
|
||||
import Bots.StoreService.FakeBTCPay (FakeBTCPay (..), setInvoiceState, withFakeBTCPay)
|
||||
import Bots.StoreService.FakeEthNode
|
||||
import Bots.StoreService.NamesContractsTests (alice, answerRegistered, answerRegistrar, launchPricesAnswer, nameExpiresCall, noncesCall, oracle, pricesCall, registrarOf, rentPriceAnswer, rentPriceCall, reservedNamesCall, unhex)
|
||||
import Bots.StoreService.NamesStoreTests (issue, order, ownerA, ownerB, placeCode, placePaid, t0, usable)
|
||||
import Bots.StoreService.NamesContractsTests (alice, answerRegistered, answerRegistrar, nameExpiresCall, noncesCall, registrarOf, rentPriceAnswer, rentPriceCall, reservedNamesCall, unhex)
|
||||
import Bots.StoreService.NamesStoreTests (order, ownerA, ownerB, placeCode, placePaid, t0)
|
||||
import Bots.StoreService.WebTests
|
||||
import Control.Concurrent (threadDelay)
|
||||
import Control.Concurrent.Async (async, concurrently, wait)
|
||||
@@ -78,7 +78,7 @@ import Simplex.Messaging.Eth.EIP712 (serializeSignature)
|
||||
import Simplex.Messaging.SimplexName (LabelHash (..), SimplexTLD (..), labelHash)
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8, tshow)
|
||||
import StoreService.Config (ChainConfig (..), NamesConfig (..), ServiceConfig (..))
|
||||
import StoreService.Names.Abi (addressWord, hex, uint256)
|
||||
import StoreService.Names.Abi (hex, uint256)
|
||||
import StoreService.Names.Chain (newChainClient)
|
||||
import StoreService.Names.Contracts (newContracts)
|
||||
import StoreService.Names.Codes (CodeRefusal (..))
|
||||
@@ -168,8 +168,8 @@ namesOrdersTests = describe "StoreService names: orders" $ do
|
||||
it "agrees between the sweep and the overdue close on which invoices hold money" testFundedAgreement
|
||||
describe "registration view" $ do
|
||||
it "has the contract's fields" testViewFields
|
||||
it "reads the intent nonce in committed and the code length in taken and code_issued" testViewReads
|
||||
it "leaves a failed intentNonce or codeLength read null and named in unread" testViewUnread
|
||||
it "reads the intent nonce in committed and nothing in taken" testViewReads
|
||||
it "leaves a failed intentNonce read null and named in unread, reading nothing in taken" testViewUnread
|
||||
|
||||
-- fixtures
|
||||
|
||||
@@ -1344,11 +1344,11 @@ testViewFields :: IO ()
|
||||
testViewFields = withServiceStore $ \st -> withFakeEthNode $ \node -> do
|
||||
Right () <- placePaid st "r1" ownerA (order "alicebakery" 1) (addUTCTime 3600 t0)
|
||||
r <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "r1")
|
||||
let v = objectOf $ viewJSON (testNamesConfig node) r ViewReads {vrIntentNonce = Nothing, vrCodeLength = Nothing, vrUnread = []}
|
||||
let v = objectOf $ viewJSON (testNamesConfig node) r ViewReads {vrIntentNonce = Nothing, vrUnread = []}
|
||||
sort (KM.keys v)
|
||||
`shouldBe` sort
|
||||
[ "id", "version", "moves", "linksRefused", "phase", "labelHash", "label", "owner", "commitment", "sealed", "resolver", "sponsor", "chainId", "tld",
|
||||
"years", "duration", "price", "codeLength", "commitTx", "registerTx", "revealAfter", "revealBefore", "intentNonce", "unread", "expiresAt", "error"
|
||||
"years", "duration", "price", "commitTx", "registerTx", "revealAfter", "revealBefore", "intentNonce", "unread", "expiresAt", "error"
|
||||
]
|
||||
map (`field` v) ["id", "phase", "owner", "tld", "chainId", "linksRefused", "label", "unread", "sponsor"]
|
||||
`shouldBe` map
|
||||
@@ -1369,26 +1369,16 @@ testViewReads = withServiceStore $ \st -> withFakeEthNode $ \node -> do
|
||||
g <- C.newRandom
|
||||
ct <- newContracts TLDSimplex =<< newChainClient g (fakeChainConfig node)
|
||||
answerCall node fakeSponsor noncesCall $ Returns $ uint256 (4 :: Int)
|
||||
answerCall node fakeController pricesCall $ Returns $ addressWord oracle
|
||||
answerCall node oracle pricesCall $ Returns launchPricesAnswer
|
||||
Right () <- placePaid st "paid" ownerA (order "alicebakery" 1) (addUTCTime 3600 t0)
|
||||
code <- issue st 7 usable
|
||||
Right () <- placeCode st "code" ownerB (order "alicebakery" 2) code
|
||||
let inPhase rid phase = do
|
||||
r <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId rid)
|
||||
let inPhase phase = do
|
||||
r <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "paid")
|
||||
Written _ <- withTransaction st $ \db -> updateRegistration db r {nrPhase = phase} t0
|
||||
withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId rid)
|
||||
committed <- inPhase "paid" PCommitted
|
||||
readViewChain st ct committed `shouldReturn` ViewReads (Just 4) Nothing []
|
||||
taken <- inPhase "paid" PTaken
|
||||
-- worth 20000 cents for 2 years at the launch list: 7 letters and longer
|
||||
readViewChain st ct taken `shouldReturn` ViewReads Nothing (Just 7) []
|
||||
takenCode <- inPhase "code" PTaken
|
||||
readViewChain st ct takenCode `shouldReturn` ViewReads Nothing Nothing []
|
||||
issued <- issue st 8 usable {npcMinLength = 9}
|
||||
Written _ <- withTransaction st $ \db -> updateRegistration db taken {nrPhase = PCodeIssued, nrIssuedCodeId = Just (pcId issued)} t0
|
||||
codeIssued <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "paid")
|
||||
readViewChain st ct codeIssued `shouldReturn` ViewReads Nothing (Just 9) []
|
||||
withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "paid")
|
||||
committed <- inPhase PCommitted
|
||||
readViewChain ct committed `shouldReturn` ViewReads (Just 4) []
|
||||
-- the oracle's prices are not answered, so any read in taken would be named in unread
|
||||
taken <- inPhase PTaken
|
||||
readViewChain ct taken `shouldReturn` ViewReads Nothing []
|
||||
|
||||
testViewUnread :: IO ()
|
||||
testViewUnread = withServiceStore $ \st -> withFakeEthNode $ \node -> do
|
||||
@@ -1397,7 +1387,7 @@ testViewUnread = withServiceStore $ \st -> withFakeEthNode $ \node -> do
|
||||
setFault node "eth_call" HttpFailure
|
||||
Right () <- placePaid st "paid" ownerA (order "alicebakery" 1) (addUTCTime 3600 t0)
|
||||
r <- withTransaction st $ \db -> fromJust <$> getRegistration db (RegistrationId "paid")
|
||||
readViewChain st ct r {nrPhase = PCommitted} `shouldReturn` ViewReads Nothing Nothing ["intentNonce"]
|
||||
readViewChain st ct r {nrPhase = PTaken} `shouldReturn` ViewReads Nothing Nothing ["codeLength"]
|
||||
let v = objectOf $ viewJSON (testNamesConfig node) r {nrPhase = PTaken} (ViewReads Nothing Nothing ["codeLength"])
|
||||
(field "codeLength" v, field "unread" v) `shouldBe` (Just J.Null, Just (J.toJSON ["codeLength" :: Text]))
|
||||
readViewChain ct r {nrPhase = PCommitted} `shouldReturn` ViewReads Nothing ["intentNonce"]
|
||||
readViewChain ct r {nrPhase = PTaken} `shouldReturn` ViewReads Nothing []
|
||||
let v = objectOf $ viewJSON (testNamesConfig node) r {nrPhase = PCommitted} (ViewReads Nothing ["intentNonce"])
|
||||
(field "intentNonce" v, field "unread" v) `shouldBe` (Just J.Null, Just (J.toJSON ["intentNonce" :: Text]))
|
||||
@@ -26,7 +26,7 @@ module Bots.StoreService.NamesOwnerTests
|
||||
where
|
||||
|
||||
import Bots.StoreService.FakeEthNode
|
||||
import Bots.StoreService.NamesContractsTests (launchPricesAnswer, oracle, pricesAnswer, pricesCall, registrarOf, rentPriceAnswer, rentPriceCall, unhex)
|
||||
import Bots.StoreService.NamesContractsTests (registrarOf, unhex)
|
||||
import Bots.StoreService.NamesOrdersTests
|
||||
import Bots.StoreService.NamesStoreTests (order, ownerA, t0)
|
||||
import Bots.StoreService.WebTests (capturingStderr, digestFixture, jsonObject, statusOf, webGet)
|
||||
@@ -40,8 +40,6 @@ import Control.Monad (forM, forM_, void)
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.KeyMap as KM
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString.Base64.URL as B64U
|
||||
import qualified Data.ByteString.Char8 as BC
|
||||
import Data.IORef (readIORef)
|
||||
import Data.Foldable (toList)
|
||||
import Data.List (find, sort, sortOn)
|
||||
@@ -53,28 +51,26 @@ import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
|
||||
import Data.Word (Word64, Word8)
|
||||
import Network.HTTP.Client (defaultManagerSettings, newManager)
|
||||
import qualified Network.Wai.Handler.Warp as Warp
|
||||
import Simplex.Chat.Badges.Types (BadgeCodePaymentStatus (..))
|
||||
import Simplex.Chat.PaymentService.Types (InvoiceId (..), InvoiceStatus (..))
|
||||
import Simplex.Messaging.Agent.Store.Common (withTransaction)
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Secp256k1 (Secp256k1PrivateKey, signRecoverable)
|
||||
import Simplex.Messaging.Eth.Address (Address)
|
||||
import Simplex.Messaging.Eth.EIP712 (serializeSignature)
|
||||
import Simplex.Messaging.SimplexName (LabelHash (..), labelHash)
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8, tshow)
|
||||
import Simplex.Messaging.Util (tshow)
|
||||
import StoreService.Config (NamesConfig (..))
|
||||
import StoreService.Names.Abi (addressWord, hex, uint256)
|
||||
import StoreService.Names.Chain (TxHash (..))
|
||||
import StoreService.Names.Orders (OrdersEnv (..), signedRequestSeconds)
|
||||
import StoreService.Names.Phases (RegistrationId (..), phasesWaiting)
|
||||
import StoreService.Names.Signatures (OrderFields (..), findMessage, parseAnyCaseAddress, personalDigest, retargetMessage, takenCodeMessage)
|
||||
import StoreService.Names.Signatures (OrderFields (..), findMessage, parseAnyCaseAddress, personalDigest, retargetMessage)
|
||||
import StoreService.Names.Store hiding (findLimit)
|
||||
import StoreService.Names.Turns (withLabelTurn)
|
||||
import StoreService.Store (RevokeResult (..))
|
||||
import StoreService.Web.Http (createLimit, findLimit, holdMicros, readLimit)
|
||||
import StoreService.Web.Names (Route (..), namesApp)
|
||||
import StoreService.Web.Names.Owner (findHandler, retargetHandler, takenCodeHandler)
|
||||
import StoreService.Web.Names.Owner (findHandler, retargetHandler)
|
||||
import StoreService.Web.Names.Registration (registrationHandler)
|
||||
import StoreService.Web.Server (WebEnv (..), webApp)
|
||||
import System.Timeout (timeout)
|
||||
@@ -102,16 +98,8 @@ namesOwnerTests = describe "StoreService names: owner" $ do
|
||||
it "refuses another signer's signature and a stale timestamp, and answers an unknown order 404" testMoveRefusedSigner
|
||||
it "answers registration_changed with the current view when the order changed during the checks, writing nothing" testMoveLostRace
|
||||
it "moves A to B while another order moves B to A, in the turns' hash order, without deadlock" testMovesCross
|
||||
describe "a code instead" $ do
|
||||
it "issues a code for a paid taken order, its minLength 6, 7 and 8 for 6, 7 and 12 letters at the launch prices, kept in the view" testTakenCode
|
||||
it "answers an identical repeat 200 whatever its codeLength and timestamp, even after a price change" testTakenCodeRepeat
|
||||
it "answers code_length_changed with the current length after a price change, issuing nothing" testCodeLengthChanged
|
||||
it "answers code_unavailable when no length qualifies, and 503 provider_unavailable when the prices cannot be read" testCodeUnavailable
|
||||
it "refuses a code for an order that is not a paid taken one, another signer, a stale timestamp and a malformed hash" testTakenCodeRefused
|
||||
it "answers code_conflict for a hash another code has, changing nothing" testCodeConflict
|
||||
it "answers registration_changed with the current view when the order changed during the length read, issuing nothing" testCodeLostRace
|
||||
describe "polls" $
|
||||
it "wakes a held registration poll by another name and by a code" testPollsWoken
|
||||
it "wakes a held registration poll by another name" testPollsWoken
|
||||
|
||||
-- fixtures
|
||||
|
||||
@@ -135,7 +123,6 @@ ownerRoutes orders = \case
|
||||
["api", "registration", "find"] -> Just Route {rMethod = "POST", rLimit = findLimit, rHandler = findHandler orders}
|
||||
["api", "registration", rid] -> Just Route {rMethod = "GET", rLimit = readLimit, rHandler = registrationHandler orders holdMicros rid}
|
||||
["api", "registration", rid, "name"] -> Just Route {rMethod = "POST", rLimit = createLimit, rHandler = retargetHandler orders rid}
|
||||
["api", "registration", rid, "code"] -> Just Route {rMethod = "POST", rLimit = createLimit, rHandler = takenCodeHandler orders rid}
|
||||
_ -> Nothing
|
||||
|
||||
signWith :: Ctx -> Secp256k1PrivateKey -> ByteString -> IO Text
|
||||
@@ -196,12 +183,6 @@ answerReserved Ctx {cNode} label reason = do
|
||||
where
|
||||
LabelHash lh = labelHash label
|
||||
|
||||
-- | The oracle's price list, as codeLengthFor reads it.
|
||||
answerPrices :: Ctx -> ByteString -> IO ()
|
||||
answerPrices Ctx {cNode} prices = do
|
||||
answerCall cNode fakeController pricesCall $ Returns $ addressWord oracle
|
||||
answerCall cNode oracle pricesCall $ Returns prices
|
||||
|
||||
-- | A paid order, settled, then written as the worker would write it.
|
||||
paidIn :: Ctx -> Order -> (NameRegistration -> NameRegistration) -> IO NameRegistration
|
||||
paidIn ctx o change = do
|
||||
@@ -294,23 +275,6 @@ findBody ctx key owner timestamp = do
|
||||
postFind :: Ctx -> J.Object -> IO (Int, Maybe J.Object)
|
||||
postFind ctx = post ctx "/api/registration/find" . J.encode
|
||||
|
||||
-- | A code hash as the page sends it: unpadded base64url of a SHA-256.
|
||||
takenCodeHash :: Int -> (Text, ByteString)
|
||||
takenCodeHash n = (T.filter (/= '=') $ safeDecodeUtf8 $ B64U.encode h, h)
|
||||
where
|
||||
h = C.sha256Hash $ "taken code " <> BC.pack (show n)
|
||||
|
||||
codeBodyAt :: Ctx -> Secp256k1PrivateKey -> RegistrationId -> Text -> Int -> Word64 -> IO J.Object
|
||||
codeBodyAt ctx key rid codeHash codeLength timestamp = do
|
||||
sig <- signWith ctx key $ takenCodeMessage rid codeHash codeLength timestamp
|
||||
pure $ KM.fromList [("codeHash", J.String codeHash), ("codeLength", J.toJSON codeLength), ("timestamp", J.toJSON timestamp), ("signature", J.String sig)]
|
||||
|
||||
postCode :: Ctx -> RegistrationId -> J.Object -> IO (Int, Maybe J.Object)
|
||||
postCode ctx rid = post ctx (registrationPath rid <> "/code") . J.encode
|
||||
|
||||
takeCode :: Ctx -> Secp256k1PrivateKey -> NameRegistration -> Text -> Int -> IO (Int, Maybe J.Object)
|
||||
takeCode ctx key NameRegistration {nrId} codeHash codeLength = clockSeconds ctx >>= codeBodyAt ctx key nrId codeHash codeLength >>= postCode ctx nrId
|
||||
|
||||
currentFields :: NameRegistration -> OrderFields
|
||||
currentFields NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment, nrSealed} =
|
||||
OrderFields {ofLabelHash = nrLabelHash, ofLength = nrLength, ofDuration = nrDuration, ofCommitment = nrCommitment, ofSealed = nrSealed}
|
||||
@@ -318,9 +282,6 @@ currentFields NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment,
|
||||
fieldsIn :: J.Object -> [(Text, J.Value)] -> Expectation
|
||||
fieldsIn v = mapM_ (\(k, value) -> (k, field k v) `shouldBe` (k, Just value))
|
||||
|
||||
answered :: Maybe J.Object
|
||||
answered = Just KM.empty
|
||||
|
||||
refusedWithView :: Text -> J.Object -> (Int, Maybe J.Object)
|
||||
refusedWithView tag view = (409, Just $ KM.fromList [("error", J.String tag), ("registration", J.Object view)])
|
||||
|
||||
@@ -329,9 +290,6 @@ labelHashText label = J.String $ hex lh
|
||||
where
|
||||
LabelHash lh = labelHash label
|
||||
|
||||
issuedCode :: Ctx -> NameRegistration -> IO PrepaidCode
|
||||
issuedCode Ctx {cStore} NameRegistration {nrIssuedCodeId} = withTransaction cStore $ \db -> fromJust <$> getPrepaidCodeById db (fromJust nrIssuedCodeId)
|
||||
|
||||
codeRow :: Ctx -> PrepaidCode -> IO PrepaidCode
|
||||
codeRow Ctx {cStore} PrepaidCode {pcId} = withTransaction cStore $ \db -> fromJust <$> getPrepaidCodeById db pcId
|
||||
|
||||
@@ -728,113 +686,6 @@ testMovesCross = withOwnerAs (\cfg -> cfg {nMaxOrderMoves = 100}) $ \ctx -> do
|
||||
where
|
||||
hashOf label = (\(LabelHash h) -> h) (labelHash label)
|
||||
|
||||
testTakenCode :: IO ()
|
||||
testTakenCode = withOwner $ \ctx@Ctx {cNode} -> do
|
||||
answerPrices ctx launchPricesAnswer
|
||||
answerCall cNode fakeController (rentPriceCall 6) $ Returns $ rentPriceAnswer 40000
|
||||
answerCall cNode fakeController (rentPriceCall 12) $ Returns $ rentPriceAnswer 10000
|
||||
forM_ (zip [1 :: Int ..] [("bakery", 40000, 6), ("dynamis", 20000, 7), ("alicebakery2", 10000, 8)]) $ \(n, (label, price, len)) -> do
|
||||
answerFree ctx label
|
||||
r <- paidIn ctx aliceOrder {oLabel = label, oPrice = price, oCommitment = commitmentOf (70 + n)} takenPhase
|
||||
field "codeLength" <$> getView ctx r `shouldReturn` Just (J.toJSON len)
|
||||
let (hashText, hash) = takenCodeHash n
|
||||
takeCode ctx keyA r hashText len `shouldReturn` (200, answered)
|
||||
written <- stored ctx r
|
||||
(nrPhase written, nrLive written, nrVersion written) `shouldBe` (PCodeIssued, False, nrVersion r + 1)
|
||||
PrepaidCode {pcCodeHash, pcMinLength, pcYears, pcPaymentStatus, pcReservedBy, pcSpentAt, pcRevokedAt, pcExpiresAt} <- issuedCode ctx written
|
||||
(pcCodeHash, pcMinLength, pcYears, pcPaymentStatus) `shouldBe` (hash, len, 2, CPSPaid)
|
||||
(pcReservedBy, pcSpentAt, pcRevokedAt, pcExpiresAt) `shouldBe` (Nothing, Nothing, Nothing, Nothing)
|
||||
v <- getView ctx r
|
||||
(field "phase" v, field "codeLength" v) `shouldBe` (Just "code_issued", Just (J.toJSON len))
|
||||
|
||||
-- | 12 letters paid 10000 cents: with these prices 8 letters and longer cost that or less.
|
||||
takenTwelve :: Ctx -> IO NameRegistration
|
||||
takenTwelve ctx@Ctx {cNode} = do
|
||||
answerPrices ctx launchPricesAnswer
|
||||
answerCall cNode fakeController (rentPriceCall 12) $ Returns $ rentPriceAnswer 10000
|
||||
answerFree ctx "alicebakery2"
|
||||
paidIn ctx aliceOrder {oLabel = "alicebakery2", oPrice = 10000} takenPhase
|
||||
|
||||
testTakenCodeRepeat :: IO ()
|
||||
testTakenCodeRepeat = withOwner $ \ctx -> do
|
||||
r <- takenTwelve ctx
|
||||
let (hashText, _) = takenCodeHash 1
|
||||
body <- clockSeconds ctx >>= codeBodyAt ctx keyA (nrId r) hashText 8
|
||||
postCode ctx (nrId r) body `shouldReturn` (200, answered)
|
||||
issued <- stored ctx r
|
||||
-- 9 letters and longer now cost 10000 cents, so a fresh computation would answer 9
|
||||
answerPrices ctx $ pricesAnswer 5000 [(8, 6000), (7, 10000), (6, 20000)]
|
||||
advance ctx 3600
|
||||
postCode ctx (nrId r) body `shouldReturn` (200, answered)
|
||||
takeCode ctx keyA r hashText 9 `shouldReturn` (200, answered)
|
||||
stored ctx r `shouldReturn` issued
|
||||
field "codeLength" <$> getView ctx r `shouldReturn` Just (J.Number 8)
|
||||
takeCode ctx keyA r (fst $ takenCodeHash 2) 9 `shouldReturn` refusedWith 400 "bad_request"
|
||||
|
||||
testCodeLengthChanged :: IO ()
|
||||
testCodeLengthChanged = withOwner $ \ctx@Ctx {cStore} -> do
|
||||
r <- takenTwelve ctx
|
||||
field "codeLength" <$> getView ctx r `shouldReturn` Just (J.Number 8)
|
||||
answerPrices ctx $ pricesAnswer 5000 [(8, 6000), (7, 10000), (6, 20000)]
|
||||
let (hashText, hash) = takenCodeHash 1
|
||||
takeCode ctx keyA r hashText 8 `shouldReturn` (409, Just $ KM.fromList [("error", "code_length_changed"), ("codeLength", J.Number 9)])
|
||||
stored ctx r `shouldReturn` r
|
||||
withTransaction cStore (`getPrepaidCode` hash) `shouldReturn` Nothing
|
||||
takeCode ctx keyA r hashText 9 `shouldReturn` (200, answered)
|
||||
pcMinLength <$> (issuedCode ctx =<< stored ctx r) `shouldReturn` 9
|
||||
|
||||
testCodeUnavailable :: IO ()
|
||||
testCodeUnavailable = withOwner $ \ctx@Ctx {cNode} -> do
|
||||
r <- takenTwelve ctx
|
||||
answerPrices ctx $ pricesAnswer 6000 []
|
||||
let (hashText, _) = takenCodeHash 1
|
||||
takeCode ctx keyA r hashText 8 `shouldReturn` refusedWith 409 "code_unavailable"
|
||||
field "codeLength" <$> getView ctx r `shouldReturn` Just J.Null
|
||||
setFault cNode "eth_call" HttpFailure
|
||||
takeCode ctx keyA r hashText 8 `shouldReturn` refusedWith 503 "provider_unavailable"
|
||||
stored ctx r `shouldReturn` r
|
||||
|
||||
testTakenCodeRefused :: IO ()
|
||||
testTakenCodeRefused = withOwner $ \ctx -> do
|
||||
r <- takenTwelve ctx
|
||||
let (hashText, _) = takenCodeHash 1
|
||||
takeCode ctx keyB r hashText 8 `shouldReturn` refusedWith 400 "bad_request"
|
||||
ts <- clockSeconds ctx
|
||||
(codeBodyAt ctx keyA (nrId r) hashText 8 (ts - signedWindow - 1) >>= postCode ctx (nrId r)) `shouldReturn` refusedWith 400 "bad_request"
|
||||
body <- codeBodyAt ctx keyA (nrId r) hashText 8 ts
|
||||
forM_ [KM.insert "codeHash" (J.String $ T.drop 1 hashText) body, KM.insert "codeLength" "8" body, KM.delete "timestamp" body] $ \malformed ->
|
||||
postCode ctx (nrId r) malformed `shouldReturn` refusedWith 400 "bad_request"
|
||||
stored ctx r `shouldReturn` r
|
||||
(codeOrder, _) <- codeIn ctx 8 aliceOrder {oCommitment = commitmentOf 5} takenPhase
|
||||
committedOrder <- paidIn ctx dynamisOrder committedPhase
|
||||
forM_ [codeOrder, committedOrder] $ \o -> do
|
||||
takeCode ctx keyA o hashText 8 `shouldReturn` refusedWith 400 "bad_request"
|
||||
stored ctx o `shouldReturn` o
|
||||
let unknown = RegistrationId "t2Jk8RmQ4vXw9NcP3yLb6g"
|
||||
(codeBodyAt ctx keyA unknown hashText 8 ts >>= postCode ctx unknown) `shouldReturn` refusedWith 404 "not_found"
|
||||
|
||||
testCodeConflict :: IO ()
|
||||
testCodeConflict = withOwner $ \ctx@Ctx {cStore} -> do
|
||||
r <- takenTwelve ctx
|
||||
let (hashText, hash) = takenCodeHash 1
|
||||
Right _ <- withTransaction cStore $ \db -> insertPrepaidCode db NewPrepaidCode {npcCodeHash = hash, npcMinLength = 8, npcYears = 2, npcPaymentStatus = CPSPaid, npcExpiresAt = Nothing} orderTime
|
||||
takeCode ctx keyA r hashText 8 `shouldReturn` refusedWith 409 "code_conflict"
|
||||
stored ctx r `shouldReturn` r
|
||||
|
||||
testCodeLostRace :: IO ()
|
||||
testCodeLostRace = withOwner $ \ctx@Ctx {cNode, cStore} -> do
|
||||
r <- takenTwelve ctx
|
||||
let (hashText, hash) = takenCodeHash 1
|
||||
-- held at the length read, inside the turn
|
||||
(issuing, gate) <- pausedAt cNode "eth_call" $ takeCode ctx keyA r hashText 8
|
||||
changed <- rewrite ctx r id
|
||||
releaseGate gate
|
||||
answer <- within "the code" $ wait issuing
|
||||
view <- getView ctx changed
|
||||
answer `shouldBe` refusedWithView "registration_changed" view
|
||||
stored ctx r `shouldReturn` changed
|
||||
withTransaction cStore (`getPrepaidCode` hash) `shouldReturn` Nothing
|
||||
|
||||
testPollsWoken :: IO ()
|
||||
testPollsWoken = withOwner $ \ctx -> do
|
||||
answerFree ctx "alicebakers"
|
||||
@@ -844,8 +695,3 @@ testPollsWoken = withOwner $ \ctx -> do
|
||||
void $ moved ctx keyA r to
|
||||
(status, v) <- woken poll
|
||||
(status, field "version" v, field "phase" v) `shouldBe` (200, Just (J.toJSON $ nrVersion r + 1), Just "queued")
|
||||
t <- takenTwelve ctx
|
||||
poll' <- held ctx t
|
||||
takeCode ctx keyA t (fst $ takenCodeHash 1) 8 `shouldReturn` (200, answered)
|
||||
(status', v') <- woken poll'
|
||||
(status', field "version" v', field "phase" v') `shouldBe` (200, Just (J.toJSON $ nrVersion t + 1), Just "code_issued")
|
||||
@@ -54,12 +54,6 @@ namesSignatureTests = describe "StoreService names: signatures" $ do
|
||||
"simplex.domains name t2Jk8RmQ4vXw9NcP3yLb6g 0x5bfbbf75311ec002834031d73fa30bfe8bdf675f4a3df72443841490c4b49d02 7 69259500 0x9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1 0xb1971beb567c8dc1b2f01f0d823754c6168d8d6830db2221702a7678e1804674 1792574100"
|
||||
"0x8bc79aa98fbd53013e8961db27554ecd17139e14ec168b86afbaf2fc910bfc52"
|
||||
"0xe3d680bcc62255179d5260beeb920319c86bd919f6b01927bee01509cf6c1fe3121151947fb9e3a310e6652c0c9a9bf7ceb48e8d903774a38a3162d916e8b4591b"
|
||||
it "signs a taken order's code, with its length" $
|
||||
testMessage
|
||||
(takenCodeMessage registrationId "7hg6ED6EotRVm21uS1KOZ-CKEzkJK5hvG4W5atfcn_Q" 7 timestamp)
|
||||
"simplex.domains code t2Jk8RmQ4vXw9NcP3yLb6g 7hg6ED6EotRVm21uS1KOZ-CKEzkJK5hvG4W5atfcn_Q 7 1792574100"
|
||||
"0x1f61297cd585c7c94218f8f87f30ea20e9b0631d36890460f24ee9bca83a2a19"
|
||||
"0x897173292eb63ac3587d3c900af08dee71f296af8718ec045dd49ce953bb0f666eefe546bea0d3623022c7cc7f03cc572a498bb0ceac26bb46ed37c9e99ecdb51b"
|
||||
describe "signature format" $ do
|
||||
it "refuses a high s, though recovery accepts it" testHighS
|
||||
it "refuses v other than 27 or 28" $
|
||||
@@ -93,9 +87,6 @@ namesSignatureTests = describe "StoreService names: signatures" $ do
|
||||
decodeString retString `shouldBe` Right "https://smp8.simplex.im/a#contact"
|
||||
decodeString (hx "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right ""
|
||||
decodeBytes (hx "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010410f13a8c833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000f73696d706c65782e636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right (hx "10f13a8c833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000f73696d706c65782e636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e7461637400000000000000000000000000000000000000000000000000000000000000")
|
||||
it "decodes the oracle's prices" $ do
|
||||
decodePrices retPrices `shouldBe` Right (5000, [(7, 10000), (6, 20000)])
|
||||
decodePrices (hx "000000000000000000000000000000000000000000000000000000000000138800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right (5000, [])
|
||||
it "refuses data that does not hold the type" $ do
|
||||
decodeUint256 (B.replicate 31 0) `shouldBe` Left AEShort
|
||||
decodeAddress (B.singleton 1 <> B.drop 1 (hx "000000000000000000000000014f9c09cceedef1b713110d04d3a07d5b5d9b73")) `shouldBe` Left AENotAddress
|
||||
@@ -103,7 +94,6 @@ namesSignatureTests = describe "StoreService names: signatures" $ do
|
||||
decodeString (uint256 (4096 :: Int) <> B.drop 32 retString) `shouldBe` Left AEOffset
|
||||
decodeString (B.take 80 retString) `shouldBe` Left AEShort
|
||||
decodeString (abiTuple [Dynamic $ abiBytes "\xff"]) `shouldBe` Left AENotUtf8
|
||||
decodePrices (B.dropEnd 32 retPrices) `shouldBe` Left AEShort
|
||||
|
||||
testMessage :: ByteString -> ByteString -> Text -> Text -> IO ()
|
||||
testMessage msg expected digest signature = do
|
||||
@@ -169,9 +159,6 @@ orderSignature = "0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6
|
||||
retString :: ByteString
|
||||
retString = hx "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e7461637400000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
retPrices :: ByteString
|
||||
retPrices = hx "0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000004e20"
|
||||
|
||||
owner :: Address
|
||||
owner = either error id $ strDecode $ encodeUtf8 ownerText
|
||||
|
||||
|
||||
@@ -252,7 +252,6 @@ testCreatePaid = withServiceStore $ \st -> do
|
||||
nrRevealAfter = Nothing,
|
||||
nrRevealBefore = Nothing,
|
||||
nrExpiresAt = Nothing,
|
||||
nrIssuedCodeId = Nothing,
|
||||
nrRegisteredAt = Nothing,
|
||||
nrFailedAt = Nothing,
|
||||
nrError = Nothing,
|
||||
@@ -386,7 +385,6 @@ testVersionBumps = withServiceStore $ \st -> do
|
||||
testRoundTrip :: IO ()
|
||||
testRoundTrip = withServiceStore $ \st -> do
|
||||
code <- issue st 1 usable
|
||||
issued <- issue st 2 usable
|
||||
placeCode st "r1" ownerA (order "alicebakery" 1) code `shouldReturn` Right ()
|
||||
r <- reg st "r1"
|
||||
let changed =
|
||||
@@ -400,7 +398,6 @@ testRoundTrip = withServiceStore $ \st -> do
|
||||
nrRevealAfter = Just 1792828800,
|
||||
nrRevealBefore = Just 1792915140,
|
||||
nrExpiresAt = Just 1861833600,
|
||||
nrIssuedCodeId = Just (pcId issued),
|
||||
nrRegisteredAt = Just (at 3),
|
||||
nrFailedAt = Just (at 2),
|
||||
nrError = Just "The name was not signed in time",
|
||||
@@ -425,7 +422,6 @@ testLiveFollowsPhase = withServiceStore $ \st -> do
|
||||
step "r1" PTaken True
|
||||
step "r1" PFailed True
|
||||
step "r1" PTaken True
|
||||
step "r1" PCodeIssued False
|
||||
step "r2" PRegistering True
|
||||
step "r2" PTaken False
|
||||
placePaid st "r3" ownerA (order "carolsbakery" 3) (at 60) `shouldReturn` Right ()
|
||||
@@ -440,7 +436,7 @@ testOrderLive = withServiceStore $ \st -> do
|
||||
let code = paid {nrPayment = RPCode 1}
|
||||
liveIn r phase = (phase, orderLive r {nrPhase = phase})
|
||||
map (liveIn paid) [minBound .. maxBound]
|
||||
`shouldBe` [(PUnpaid, True), (PQueued, True), (PCommitting, True), (PCommitted, True), (PRegistering, True), (PRegistered, False), (PTaken, True), (PFailed, True), (PCodeIssued, False)]
|
||||
`shouldBe` [(PUnpaid, True), (PQueued, True), (PCommitting, True), (PCommitted, True), (PRegistering, True), (PRegistered, False), (PTaken, True), (PFailed, True)]
|
||||
map (liveIn code) [PTaken, PFailed, PRegistered] `shouldBe` [(PTaken, False), (PFailed, True), (PRegistered, False)]
|
||||
orderLive paid {nrLive = False} `shouldBe` False
|
||||
orderLive paid {nrLive = False, nrPhase = PQueued} `shouldBe` True
|
||||
|
||||
Reference in new issue
Block a user