2022-11-10 05:50:08 -08:00
package finalizers
import (
"context"
"fmt"
"github.com/certusone/wormhole/node/pkg/watchers/evm/connectors"
2022-11-14 06:07:45 -08:00
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
2022-11-10 05:50:08 -08:00
"go.uber.org/zap"
)
// OptimismFinalizer implements the finality check for Optimism.
// Optimism provides a special "rollup_getInfo" API call to determine the latest L2 (Optimism) block to be published on the L1 (Ethereum).
// This finalizer polls that API to determine if a block is finalized.
type FinalizerEntry struct {
l2Block uint64
l1Block uint64
}
type OptimismFinalizer struct {
logger * zap . Logger
connector connectors . Connector
l1Finalizer interfaces . L1Finalizer
latestFinalizedL2Block uint64
// finalizerMapping is a array of FinalizerEntry structs with the L2 block number that has been verified and its corresponding L1 block number
finalizerMapping [ ] FinalizerEntry
}
func NewOptimismFinalizer ( ctx context . Context , logger * zap . Logger , connector connectors . Connector , l1Finalizer interfaces . L1Finalizer ) * OptimismFinalizer {
return & OptimismFinalizer {
logger : logger ,
connector : connector ,
l1Finalizer : l1Finalizer ,
latestFinalizedL2Block : 0 ,
finalizerMapping : make ( [ ] FinalizerEntry , 0 ) ,
}
}
func ( f * OptimismFinalizer ) IsBlockFinalized ( ctx context . Context , block * connectors . NewBlock ) ( bool , error ) {
finalizedL1Block := f . l1Finalizer . GetLatestFinalizedBlockNumber ( )
if finalizedL1Block == 0 {
// This happens on start up.
return false , nil
}
// Result is the json information coming back from the Optimism node's rollup_getInfo() call
type Result struct {
Mode string
EthContext struct {
BlockNumber uint64 ` json:"blockNumber" `
TimeStamp uint64 ` json:"timestamp" `
} ` json:"ethContext" `
RollupContext struct {
Index uint64 ` json:"index" `
VerifiedIndex uint64 ` json:"verifiedIndex" `
} ` json:"rollupContext" `
}
// Always call into the Optimism node to get the latest rollup information so we don't have to wait
// any longer than is necessary for finality by skipping rollup info messages
var info Result
err := f . connector . RawCallContext ( ctx , & info , "rollup_getInfo" )
if err != nil {
// This is the error case where the RPC call fails
f . logger . Error ( "failed to get rollup info" , zap . String ( "eth_network" , f . connector . NetworkName ( ) ) , zap . Error ( err ) )
return false , err
}
if info . RollupContext . VerifiedIndex == 0 {
// This is the error case where the RPC call is not working as expected.
return false , fmt . Errorf ( "Received a verified index of 0. Please check Optimism RPC parameter." )
}
f . logger . Debug ( "finalizerMapping" , zap . Uint64 ( "L2 verified index" , info . RollupContext . VerifiedIndex ) , zap . String ( " => " , "" ) , zap . Uint64 ( "L1_blockNumber" , info . EthContext . BlockNumber ) )
// Look at the last element of the array and see if we need to add this entry
// The assumption here is that every subsequent call moves forward (or stays the same). It is an error if verifiedIndex goes backwards
finalizerMappingSize := len ( f . finalizerMapping )
if finalizerMappingSize != 0 && f . finalizerMapping [ finalizerMappingSize - 1 ] . l2Block > info . RollupContext . VerifiedIndex {
// This is the error case where the RPC call is not working as expected.
return false , fmt . Errorf ( "The received verified index just went backwards. Received %d. Last number in array is %d" , info . RollupContext . VerifiedIndex , f . finalizerMapping [ finalizerMappingSize - 1 ] . l2Block )
}
if finalizerMappingSize == 0 || f . finalizerMapping [ finalizerMappingSize - 1 ] . l2Block < info . RollupContext . VerifiedIndex {
// New information. Append it to the array.
f . finalizerMapping = append ( f . finalizerMapping , FinalizerEntry { l2Block : info . RollupContext . VerifiedIndex , l1Block : info . EthContext . BlockNumber } )
f . logger . Info ( "Appending new entry." , zap . Int ( "finalizerMap size" , len ( f . finalizerMapping ) ) , zap . Uint64 ( "L2 verified index" , info . RollupContext . VerifiedIndex ) , zap . Uint64 ( "L1_blockNumber" , info . EthContext . BlockNumber ) )
}
// Here we want to prune the known finalized entries from the mapping, while recording the latest finalized L2 block number
pruneIdx := - 1
for idx , entry := range f . finalizerMapping {
if entry . l1Block > finalizedL1Block {
break
}
// The L1 block for this entry has been finalized so we can prune it.
f . latestFinalizedL2Block = entry . l2Block
pruneIdx = idx
}
if pruneIdx >= 0 {
// Do the pruning here
if pruneIdx + 1 >= len ( f . finalizerMapping ) {
f . finalizerMapping = nil
} else {
f . finalizerMapping = f . finalizerMapping [ pruneIdx + 1 : ]
}
f . logger . Info ( "Pruning finalizerMapping" , zap . Int ( "Pruning from index" , pruneIdx ) , zap . Int ( "new array size" , len ( f . finalizerMapping ) ) )
}
isFinalized := block . Number . Uint64 ( ) <= f . latestFinalizedL2Block
f . logger . Debug ( "got rollup info" , zap . String ( "eth_network" , f . connector . NetworkName ( ) ) ,
zap . Bool ( "isFinalized" , isFinalized ) ,
zap . String ( "mode" , info . Mode ) ,
zap . Uint64 ( "l1_blockNumber" , info . EthContext . BlockNumber ) ,
zap . Uint64 ( "l1_finalizedBlock" , finalizedL1Block ) ,
zap . Uint64 ( "l2_blockNumber" , info . RollupContext . Index ) ,
zap . Uint64 ( "verified_index" , info . RollupContext . VerifiedIndex ) ,
zap . Uint64 ( "latestFinalizedL2Block" , f . latestFinalizedL2Block ) ,
zap . Stringer ( "desired_block" , block . Number ) ,
)
return isFinalized , nil
}
/ *
curl - X POST -- data ' { "jsonrpc" : "2.0" , "method" : "rollup_getInfo" , "params" : [ ] , "id" : 1 } ' https : //rpc.ankr.com/optimism_testnet
{
"jsonrpc" : "2.0" , "id" : 1 , "result" : {
"mode" : "verifier" ,
"syncing" : false ,
"ethContext" : {
"blockNumber" : 7763392 , "timestamp" : 1665680949 // This is a few blocks behind the latest block on goerli.
} ,
"rollupContext" : {
"index" : 1952690 , "queueIndex" : 13285 , "verifiedIndex" : 0 // This is a few blocks behind the latest block on optimism.
}
}
}
* /