retry different relay upon failure

This commit is contained in:
Alain Brenzikofer
2026-09-23 10:57:17 +02:00
parent b974dbd8c8
commit 21164fce3c
3 changed files with 49 additions and 6 deletions
+4 -1
View File
@@ -17,7 +17,10 @@ ownedSimplexNames agent, over ownedNames in the server
```
The agent returns the relay it used alongside the answer, so a caller can pass
it back as used and ask the next account elsewhere. Sending every account of a
it back as used and ask the next account elsewhere. A relay that cannot answer
at all - too old for ROWN, unreachable, or with no resolver of its own behind it
- is not the end of the lookup: it asks another, so a scan does not fail
wholesale on the one relay it happened to draw during a version rollout. Sending every account of a
seed to one relay would tell that relay the accounts belong to one wallet; the
scan therefore asks one account per relay, and `getNextNameServer` avoids the
hosts already used where the configured set allows. `proxiedSMPRelayVersion` is
+16 -4
View File
@@ -228,7 +228,7 @@ import Simplex.Messaging.Parsers (defaultJSON, parse)
import Simplex.Messaging.Protocol
( BrokerMsg,
Cmd (..),
ErrorType (AUTH),
ErrorType (AUTH, NAME),
MsgBody,
MsgFlags (..),
NameResponse,
@@ -1280,9 +1280,21 @@ deleteLocalInvShortLink' :: AgentClient -> ConnShortLink 'CMInvitation -> AM ()
deleteLocalInvShortLink' c (CSLInvitation _ srv linkId _) = withStore' c $ \db -> deleteInvShortLink db srv linkId
ownedSimplexNames' :: AgentClient -> NetworkRequestMode -> UserId -> [SMPServer] -> Address -> Word32 -> AM (SMPServer, OwnedNames)
ownedSimplexNames' c nm userId used addr offset = do
srv <- getNextNameServer c userId used
(srv,) <$> ownedNames c nm userId srv addr offset
ownedSimplexNames' c nm userId used addr offset = tryRelays ownedNamesRelays used
where
tryRelays attempts tried = do
srv <- getNextNameServer c userId tried
((srv,) <$> ownedNames c nm userId srv addr offset) `catchError` \e ->
if attempts > 1 && cannotAnswer e then tryRelays (attempts - 1) (srv : tried) else throwE e
-- a relay that cannot answer, as against one that answers: too old for ROWN,
-- unreachable, or with no resolver of its own behind it
cannotAnswer e = temporaryOrHostError e || case e of
SMP _ (NAME _) -> True
_ -> False
-- | Relays one owned-names lookup asks before it gives up: during a version rollout the first one picked may have no ROWN.
ownedNamesRelays :: Int
ownedNamesRelays = 3
resolveSimplexName' :: AgentClient -> NetworkRequestMode -> UserId -> SimplexDomain -> AM NameResponse
resolveSimplexName' c nm userId domain = do
+29 -1
View File
@@ -15,6 +15,7 @@ module AgentTests.ResolveNameTests (resolveNameTests) where
import AgentTests.FunctionalAPITests (withAgent)
import Control.Monad.Except (runExceptT)
import qualified Data.ByteString.Lazy as LB
import Data.IORef (IORef, readIORef)
import Data.List (isInfixOf)
import Network.HTTP.Types (Status, status200, status404, status502)
import NamesResolverServer (memCfg, memCfg2, memProxyCfg, withNames)
@@ -22,7 +23,10 @@ import qualified NamesResolverServer as NRS
import SMPAgentClient
import SMPClient
import SMPNamesTests (availableBody, registeredBody, testNameRecord)
import Simplex.Messaging.Agent (resolveSimplexName)
import Data.Text (Text)
import Simplex.Messaging.Agent (ownedSimplexNames, resolveSimplexName)
import Simplex.Messaging.Encoding.String (strDecode)
import Simplex.Messaging.Eth.Address (Address)
import Simplex.Messaging.Agent.Client (AgentClient)
import Simplex.Messaging.Agent.Env.SQLite (InitialAgentServers (..), ServerCfg, ServerRoles (..), presetServerCfg)
import Simplex.Messaging.Agent.Protocol (AgentErrorType (..))
@@ -49,6 +53,13 @@ withDirectResolver (st, body) k =
withSmpServerConfigOn (transport @TLS) (withNames port memCfg) testPort $ \_ ->
withAgent 1 agentCfg (oneSrv (nameSrvCfg testSMPServer)) testDB k
-- | As 'withDirectResolver', with the requests the resolver was asked for.
withDirectResolverReqs :: (Status, LB.ByteString) -> (AgentClient -> IORef [[Text]] -> IO a) -> IO a
withDirectResolverReqs (st, body) k =
NRS.withResolverServer (NRS.resolveResp st body) $ \port reqs ->
withSmpServerConfigOn (transport @TLS) (withNames port memCfg) testPort $ \_ ->
withAgent 1 agentCfg (oneSrv (nameSrvCfg testSMPServer)) testDB $ \c -> k c reqs
withProxyAndResolver :: (Status, LB.ByteString) -> (AgentClient -> IO a) -> IO a
withProxyAndResolver (st, body) k =
NRS.withResolverServer (NRS.resolveResp st body) $ \port _ ->
@@ -87,6 +98,23 @@ resolveNameTests = do
it "returns NameRecord" testDirectSuccess
describe "name availability" $
it "an unregistered name answers as available" testAvailSuccess
describe "owned names" $
it "a relay that cannot answer is not the end of the lookup" testOwnedRetriesRelays
-- | A relay too old for ROWN, unreachable, or with no resolver behind it must
-- not end a recovery scan at the account it was asked about.
testOwnedRetriesRelays :: HasCallStack => IO ()
testOwnedRetriesRelays =
withDirectResolverReqs (status502, "{}") $ \c reqs -> do
r <- runExceptT $ ownedSimplexNames c NRMInteractive 1 [] testAddr 0
case r of
Left (SMP _ (SMP.NAME _)) -> pure ()
_ -> expectationFailure $ "expected Left (SMP _ (NAME ..)), got: " <> show r
asked <- readIORef reqs
length [q | q@("v2" : "owned-by" : _) <- asked] `shouldBe` 3
testAddr :: Address
testAddr = either error id $ strDecode "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
testAvailSuccess :: HasCallStack => IO ()
testAvailSuccess =