1
1
import CopyIcon from '@atlaskit/icon/glyph/copy'
2
- import React , { useMemo , useState } from 'react'
2
+ import React , { useMemo , useState , useEffect } from 'react'
3
3
import styled from 'styled-components'
4
4
import { toaster } from 'toasterhea'
5
5
import { Alert } from '~/components/Alert'
@@ -9,7 +9,7 @@ import { getSelfDelegationFraction } from '~/getters'
9
9
import { useConfigValueFromChain , useMediaQuery } from '~/hooks'
10
10
import { useAllOperatorsForWalletQuery } from '~/hooks/operators'
11
11
import { useSponsorshipTokenInfo } from '~/hooks/sponsorships'
12
- import { useInterceptHeartbeats } from '~/hooks/useInterceptHeartbeats'
12
+ import { Heartbeat , useInterceptHeartbeats } from '~/hooks/useInterceptHeartbeats'
13
13
import useOperatorLiveNodes from '~/hooks/useOperatorLiveNodes'
14
14
import { SelectField2 } from '~/marketplace/components/SelectField2'
15
15
import FormModal , {
@@ -35,6 +35,7 @@ import Label from '~/shared/components/Ui/Label'
35
35
import useCopy from '~/shared/hooks/useCopy'
36
36
import { useWalletAccount } from '~/shared/stores/wallet'
37
37
import Toast from '~/shared/toasts/Toast'
38
+ import { isNodeVersionGreaterThanOrEqualTo } from '~/shared/utils/nodeVersion'
38
39
import { COLORS } from '~/shared/utils/styled'
39
40
import { truncate } from '~/shared/utils/text'
40
41
import { humanize } from '~/shared/utils/time'
@@ -55,6 +56,8 @@ interface Props extends Pick<FormModalProps, 'onReject'> {
55
56
56
57
const limitErrorToaster = toaster ( Toast , Layer . Toast )
57
58
59
+ const requiredNodeVersion = '102.0.0'
60
+
58
61
function JoinSponsorshipModal ( {
59
62
chainId,
60
63
onResolve,
@@ -295,10 +298,6 @@ function JoinSponsorshipModal({
295
298
</ li >
296
299
</ PropList >
297
300
</ Section >
298
- < StyledAlert type = "notice" title = "Node version check" >
299
- Node versions 100.2.3 and below are vulnerable to slashing. Ensure that
300
- all of your nodes are not running an outdated version.
301
- </ StyledAlert >
302
301
{ isBelowSelfFundingLimit && (
303
302
< StyledAlert type = "error" title = "Low self-funding" >
304
303
You cannot stake on Sponsorships because your Operator is below the
@@ -317,9 +316,10 @@ function JoinSponsorshipModal({
317
316
</ StyledAlert >
318
317
) }
319
318
{ ! isBelowSelfFundingLimit && ! hasUndelegationQueue && (
320
- < LiveNodesCheck
319
+ < LiveNodesAndVersionCheck
321
320
liveNodesCountLoading = { liveNodesCountLoading }
322
321
liveNodesCount = { liveNodesCount }
322
+ heartbeats = { heartbeats }
323
323
/>
324
324
) }
325
325
{ sponsorship . minimumStakingPeriodSeconds > 0 && (
@@ -343,21 +343,65 @@ function JoinSponsorshipModal({
343
343
interface LiveNodesCheckProps {
344
344
liveNodesCountLoading : boolean
345
345
liveNodesCount : number
346
+ heartbeats : Record < string , Heartbeat | undefined >
346
347
}
347
348
348
- function LiveNodesCheck ( { liveNodesCountLoading, liveNodesCount } : LiveNodesCheckProps ) {
349
- if ( liveNodesCountLoading ) {
349
+ function LiveNodesAndVersionCheck ( {
350
+ liveNodesCountLoading,
351
+ liveNodesCount,
352
+ heartbeats,
353
+ } : LiveNodesCheckProps ) {
354
+ const [ hasWaitedForHeartbeats , setHasWaitedForHeartbeats ] = useState ( false )
355
+
356
+ useEffect ( ( ) => {
357
+ const timer = setTimeout ( ( ) => {
358
+ setHasWaitedForHeartbeats ( true )
359
+ } , 6000 ) // Wait for 6 seconds to ensure heartbeats are available from ~all nodes
360
+
361
+ return ( ) => clearTimeout ( timer )
362
+ } , [ ] )
363
+
364
+ if ( ! hasWaitedForHeartbeats || liveNodesCountLoading ) {
350
365
return (
351
366
< StyledAlert type = "loading" title = "Checking Streamr nodes" >
352
367
< span >
353
- In order to continue, you need to have one or more Streamr nodes
354
- running and correctly configured. You will be slashed if you stake
355
- without your nodes contributing resources to the stream.
368
+ In order to continue, you need to have, 1 or more Streamr nodes
369
+ running, and have them all be correctly configured. All of your nodes
370
+ must be running version { requiredNodeVersion } or higher. You are
371
+ vulnerable to slashing if you stake with misconfigured nodes.
356
372
</ span >
357
373
</ StyledAlert >
358
374
)
359
375
}
360
376
377
+ const nodeVersions = Object . values ( heartbeats ) . map (
378
+ ( heartbeat ) => heartbeat ?. applicationVersion ,
379
+ )
380
+ const areAllNodesRunningRequiredVersion = nodeVersions . every ( ( version ) => {
381
+ // If version is undefined (old node) or null, treat it as not meeting the requirement
382
+ if ( ! version ) {
383
+ return false
384
+ }
385
+ return isNodeVersionGreaterThanOrEqualTo ( version , requiredNodeVersion )
386
+ } )
387
+ if ( ! areAllNodesRunningRequiredVersion ) {
388
+ return (
389
+ < StyledAlert type = "error" title = "Streamr nodes are outdated" >
390
+ < p >
391
+ The minimum required version for all of your Streamr nodes is{ ' ' }
392
+ { requiredNodeVersion } or higher. Please update your nodes.
393
+ </ p >
394
+ < a
395
+ href = { R . docs ( '/guides/how-to-update-your-streamr-node/' ) }
396
+ target = "_blank"
397
+ rel = "noreferrer noopener"
398
+ >
399
+ How to upgrade a Streamr node < LinkIcon name = "externalLink" />
400
+ </ a >
401
+ </ StyledAlert >
402
+ )
403
+ }
404
+
361
405
if ( liveNodesCount > 0 ) {
362
406
return (
363
407
< StyledAlert type = "success" title = "Streamr nodes detected" >
0 commit comments