72 lines
2.9 KiB
Go
72 lines
2.9 KiB
Go
package finalizers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/certusone/wormhole/node/pkg/watchers/evm/connectors"
|
|
|
|
arbitrumAbi "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors/arbitrumabi"
|
|
ethBind "github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
|
ethClient "github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// ArbitrumFinalizer implements the finality check for Arbitrum.
|
|
// Arbitrum blocks should not be considered finalized until they show up on Ethereum.
|
|
// To determine when a block is final, we have to query the NodeInterface precompiled contract on Arbitrum.
|
|
|
|
// To build the ABI for the NodeInterface precomile, do the following:
|
|
// - Download this file to ethereum/contracts/NodeInterface.sol and build the contracts.
|
|
// https://developer.offchainlabs.com/assets/files/NodeInterface-1413a19adf5bfcf97f5a5d3207d1b452.sol
|
|
// - Edit ethereum/build/contracts/NodeInterface.json and delete all but the bracketed part of “abi:”.
|
|
// - cd thirdparty/abigen and do the following to build the abigen tool:
|
|
// go build -mod=readonly -o abigen github.com/ethereum/go-ethereum/cmd/abigen
|
|
// - cd to the wormhole directory and do the following:
|
|
// - mkdir node/pkg/watchers/evm/connectors/arbitrumabi
|
|
// - third_party/abigen/abigen --abi ethereum/build/contracts/NodeInterface.json --pkg abi_arbitrum --out node/pkg/watchers/evm/connectors/arbitrumabi/abi.go
|
|
|
|
type ArbitrumFinalizer struct {
|
|
logger *zap.Logger
|
|
connector connectors.Connector
|
|
caller *arbitrumAbi.AbiArbitrumCaller
|
|
}
|
|
|
|
const (
|
|
arbitrumNodeInterfacePrecompileAddr = "0x00000000000000000000000000000000000000C8"
|
|
)
|
|
|
|
func NewArbitrumFinalizer(logger *zap.Logger, connector connectors.Connector, client *ethClient.Client) *ArbitrumFinalizer {
|
|
caller, err := arbitrumAbi.NewAbiArbitrumCaller(ethCommon.HexToAddress(arbitrumNodeInterfacePrecompileAddr), client)
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to create Arbitrum finalizer: %w", err))
|
|
}
|
|
|
|
return &ArbitrumFinalizer{
|
|
logger: logger,
|
|
connector: connector,
|
|
caller: caller,
|
|
}
|
|
}
|
|
|
|
// IsBlockFinalized queries the NodeInfrastructure precompiled contract to see if the L2 (Arbitrum) block has appeared
|
|
// in an L1 (Ethereum) block. We don't really care what L2 block it appeared in, just that it has.
|
|
func (a *ArbitrumFinalizer) IsBlockFinalized(ctx context.Context, block *connectors.NewBlock) (bool, error) {
|
|
_, err := a.caller.FindBatchContainingBlock(ðBind.CallOpts{Context: ctx}, block.Number.Uint64())
|
|
if err != nil {
|
|
// If it hasn't been published yet, the method returns an error, so we check for that and treat it as
|
|
// not finalized, rather than as an error. Here's what that looks like:
|
|
// "requested block 430842 is after latest on-chain block 430820 published in batch 4686"
|
|
if strings.ContainsAny(err.Error(), "is after latest on-chain block") {
|
|
return false, nil
|
|
}
|
|
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|