@@ -31,6 +31,7 @@ import {
3131 version ,
3232 InitError ,
3333 InvalidParamType ,
34+ NetworkError ,
3435 NodeError ,
3536 UnknownError ,
3637 InvalidArgumentException ,
@@ -118,6 +119,8 @@ export type LitNodeClientConfigWithDefaults = Required<
118119const EPOCH_PROPAGATION_DELAY = 45_000 ;
119120// This interval is responsible for keeping latest block hash up to date
120121const BLOCKHASH_SYNC_INTERVAL = 30_000 ;
122+ // When fetching the blockhash from a provider (not lit), we use a previous block to avoid a nodes not knowing about the new block yet
123+ const BLOCKHASH_COUNT_PROVIDER_DELAY = - 1 ;
121124
122125// Intentionally not including datil-dev here per discussion with Howard
123126const NETWORKS_REQUIRING_SEV : string [ ] = [
@@ -784,6 +787,8 @@ export class LitCore {
784787
785788 /**
786789 * Fetches the latest block hash and log any errors that are returned
790+ * Nodes will accept any blockhash in the last 30 days but use the latest 10 as challenges for webauthn
791+ * Note: last blockhash from providers might not be propagated to the nodes yet, so we need to use a slightly older one
787792 * @returns void
788793 */
789794 private async _syncBlockhash ( ) {
@@ -805,52 +810,72 @@ export class LitCore {
805810 this . latestBlockhash
806811 ) ;
807812
808- return fetch ( this . _blockHashUrl )
809- . then ( async ( resp : Response ) => {
810- const blockHashBody : EthBlockhashInfo = await resp . json ( ) ;
811- this . latestBlockhash = blockHashBody . blockhash ;
812- this . lastBlockHashRetrieved = Date . now ( ) ;
813- log ( 'Done syncing state new blockhash: ' , this . latestBlockhash ) ;
814-
815- // If the blockhash retrieval failed, throw an error to trigger fallback in catch block
816- if ( ! this . latestBlockhash ) {
817- throw new Error (
818- `Error getting latest blockhash. Received: "${ this . latestBlockhash } "`
819- ) ;
820- }
821- } )
822- . catch ( async ( err : BlockHashErrorResponse | Error ) => {
823- logError (
824- 'Error while attempting to fetch new latestBlockhash:' ,
825- err instanceof Error ? err . message : err . messages ,
826- 'Reason: ' ,
827- err instanceof Error ? err : err . reason
813+ try {
814+ // This fetches from the lit propagation service so nodes will always have it
815+ const resp = await fetch ( this . _blockHashUrl ) ;
816+ // If the blockhash retrieval failed, throw an error to trigger fallback in catch block
817+ if ( ! resp . ok ) {
818+ throw new NetworkError (
819+ {
820+ responseResult : resp . ok ,
821+ responseStatus : resp . status ,
822+ } ,
823+ `Error getting latest blockhash from ${ this . _blockHashUrl } . Received: "${ resp . status } "`
828824 ) ;
825+ }
829826
830- log (
831- 'Attempting to fetch blockhash manually using ethers with fallback RPC URLs...'
827+ const blockHashBody : EthBlockhashInfo = await resp . json ( ) ;
828+ const { blockhash, timestamp } = blockHashBody ;
829+
830+ // If the blockhash retrieval does not have the required fields, throw an error to trigger fallback in catch block
831+ if ( ! blockhash || ! timestamp ) {
832+ throw new NetworkError (
833+ {
834+ responseResult : resp . ok ,
835+ blockHashBody,
836+ } ,
837+ `Error getting latest blockhash from block indexer. Received: "${ blockHashBody } "`
832838 ) ;
833- const provider = await this . _getProviderWithFallback ( ) ;
839+ }
834840
835- if ( ! provider ) {
836- logError (
837- 'All fallback RPC URLs failed. Unable to retrieve blockhash.'
838- ) ;
839- return ;
840- }
841+ this . latestBlockhash = blockHashBody . blockhash ;
842+ this . lastBlockHashRetrieved = parseInt ( timestamp ) * 1000 ;
843+ log ( 'Done syncing state new blockhash: ' , this . latestBlockhash ) ;
844+ } catch ( error : unknown ) {
845+ const err = error as BlockHashErrorResponse | Error ;
846+
847+ logError (
848+ 'Error while attempting to fetch new latestBlockhash:' ,
849+ err instanceof Error ? err . message : err . messages ,
850+ 'Reason: ' ,
851+ err instanceof Error ? err : err . reason
852+ ) ;
841853
842- try {
843- const latestBlock = await provider . getBlock ( 'latest' ) ;
844- this . latestBlockhash = latestBlock . hash ;
845- this . lastBlockHashRetrieved = Date . now ( ) ;
846- log (
847- 'Successfully retrieved blockhash manually: ' ,
848- this . latestBlockhash
849- ) ;
850- } catch ( ethersError ) {
851- logError ( 'Failed to manually retrieve blockhash using ethers' ) ;
852- }
853- } ) ;
854+ log (
855+ 'Attempting to fetch blockhash manually using ethers with fallback RPC URLs...'
856+ ) ;
857+ const provider = await this . _getProviderWithFallback ( ) ;
858+
859+ if ( ! provider ) {
860+ logError (
861+ 'All fallback RPC URLs failed. Unable to retrieve blockhash.'
862+ ) ;
863+ return ;
864+ }
865+
866+ try {
867+ // We use a previous block to avoid nodes not having received the latest block yet
868+ const priorBlock = await provider . getBlock ( BLOCKHASH_COUNT_PROVIDER_DELAY ) ;
869+ this . latestBlockhash = priorBlock . hash ;
870+ this . lastBlockHashRetrieved = priorBlock . timestamp ;
871+ log (
872+ 'Successfully retrieved blockhash manually: ' ,
873+ this . latestBlockhash
874+ ) ;
875+ } catch ( ethersError ) {
876+ logError ( 'Failed to manually retrieve blockhash using ethers' ) ;
877+ }
878+ }
854879 }
855880
856881 /** Currently, we perform a full sync every 30s, including handshaking with every node
0 commit comments