Skip to content

Commit

Permalink
feat: make blocktracker aware of block hashes (#400)
Browse files Browse the repository at this point in the history
Changelog
--
- Make `blockTracker` aware of block hashes. This is the first step to
enhance the indexer.
- Minor format fixes.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

Based on the comprehensive summary, here are the updated release notes:

- **New Features**
- Enhanced block tracking with block hash support across multiple
components.
- Added new utility functions for generating and converting block
hashes.

- **Improvements**
  - Updated database schema to include block hash tracking.
  - Standardized error message capitalization across various packages.
  - Refined block indexing and tracking mechanisms.

- **Chores**
  - Updated configuration files for local network contracts.
- Modified mock interfaces to support new block tracking functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
fbac authored Jan 10, 2025
1 parent 084f809 commit 235c2be
Show file tree
Hide file tree
Showing 17 changed files with 207 additions and 82 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ __debug_*
build/
bin/

# contracts development config
contracts/config/anvil_localnet/

# Test binary, built with `go test -c`
*.test

Expand Down
4 changes: 2 additions & 2 deletions pkg/blockchain/registryAdmin.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ func (n *NodeRegistryAdmin) AddNode(
httpAddress string,
) error {
if !common.IsHexAddress(owner) {
return fmt.Errorf("Invalid owner address provided %s", owner)
return fmt.Errorf("invalid owner address provided %s", owner)
}

ownerAddress := common.HexToAddress(owner)
signingKey := crypto.FromECDSAPub(signingKeyPub)

if n.signer == nil {
return fmt.Errorf("No signer provided")
return fmt.Errorf("no signer provided")
}
tx, err := n.contract.AddNode(&bind.TransactOpts{
Context: ctx,
Expand Down
2 changes: 1 addition & 1 deletion pkg/blockchain/registryAdmin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestAddNodeBadOwner(t *testing.T) {
owner := testutils.RandomString(10)

err := registry.AddNode(ctx, owner, &privateKey.PublicKey, httpAddress)
require.ErrorContains(t, err, "Invalid owner address")
require.ErrorContains(t, err, "invalid owner address provided")
}

func TestAddNodeUnauthorized(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/blockchain/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func NewPrivateKeySigner(privateKeyString string, chainID int) (*PrivateKeySigne
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("Failed to cast to ECDSA public key %v", err)
return nil, fmt.Errorf("failed to cast to ECDSA public key %v", err)
}

fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
Expand All @@ -42,7 +42,7 @@ func NewPrivateKeySigner(privateKeyString string, chainID int) (*PrivateKeySigne
big.NewInt(int64(chainID)),
)
if err != nil {
return nil, fmt.Errorf("Failed to create transactor: %v", err)
return nil, fmt.Errorf("failed to create transactor: %v", err)
}

return &PrivateKeySigner{
Expand Down
12 changes: 7 additions & 5 deletions pkg/db/queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,19 @@ WHERE
originator_node_id = @originator_node_id;

-- name: SetLatestBlock :exec
INSERT INTO latest_block(contract_address, block_number)
VALUES (@contract_address, @block_number)
INSERT INTO latest_block(contract_address, block_number, block_hash)
VALUES (@contract_address, @block_number, @block_hash)
ON CONFLICT (contract_address)
DO UPDATE SET
block_number = @block_number
block_number = @block_number, block_hash = @block_hash
WHERE
@block_number > latest_block.block_number;
@block_number > latest_block.block_number
AND @block_hash != latest_block.block_hash;

-- name: GetLatestBlock :one
SELECT
block_number
block_number,
block_hash
FROM
latest_block
WHERE
Expand Down
1 change: 1 addition & 0 deletions pkg/db/queries/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 17 additions & 9 deletions pkg/db/queries/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 54 additions & 17 deletions pkg/indexer/blockTracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sync"
"sync/atomic"

"github.com/ethereum/go-ethereum/common"
"github.com/xmtp/xmtpd/pkg/db/queries"
)

Expand All @@ -18,12 +19,21 @@ and allows the user to increase the value.
*
*/
type BlockTracker struct {
latestBlock atomic.Uint64
latestBlock *Block
contractAddress string
queries *queries.Queries
mu sync.Mutex
}

type Block struct {
number atomic.Uint64
hash common.Hash
}

var (
ErrEmptyBlockHash = errors.New("block hash is empty")
)

// Return a new BlockTracker initialized to the latest block from the DB
func NewBlockTracker(
ctx context.Context,
Expand All @@ -39,64 +49,91 @@ func NewBlockTracker(
if err != nil {
return nil, err
}
bt.latestBlock.Store(latestBlock)
bt.latestBlock = latestBlock

return bt, nil
}

func (bt *BlockTracker) GetLatestBlock() uint64 {
return bt.latestBlock.Load()
func (bt *BlockTracker) GetLatestBlock() (uint64, []byte) {
bt.mu.Lock()
defer bt.mu.Unlock()
return bt.latestBlock.number.Load(), bt.latestBlock.hash.Bytes()
}

func (bt *BlockTracker) UpdateLatestBlock(ctx context.Context, block uint64) error {
func (bt *BlockTracker) UpdateLatestBlock(
ctx context.Context,
block uint64,
hashBytes []byte,
) error {
// Quick check without lock
if block <= bt.latestBlock.Load() {
if block <= bt.latestBlock.number.Load() {
return nil
}

bt.mu.Lock()
defer bt.mu.Unlock()

// Re-check after acquiring lock
if block <= bt.latestBlock.Load() {
if block <= bt.latestBlock.number.Load() {
return nil
}

if err := bt.updateDB(ctx, block); err != nil {
newHash := common.Hash(hashBytes)

if newHash == (common.Hash{}) {
return ErrEmptyBlockHash
}

if newHash == bt.latestBlock.hash {
return nil
}

if err := bt.updateDB(ctx, block, newHash.Bytes()); err != nil {
return err
}

bt.latestBlock.Store(block)
bt.latestBlock.number.Store(block)
bt.latestBlock.hash = newHash

return nil
}

func (bt *BlockTracker) updateDB(ctx context.Context, block uint64) error {
func (bt *BlockTracker) updateDB(ctx context.Context, block uint64, hash []byte) error {
return bt.queries.SetLatestBlock(ctx, queries.SetLatestBlockParams{
ContractAddress: bt.contractAddress,
BlockNumber: int64(block),
BlockHash: hash,
})
}

func loadLatestBlock(
ctx context.Context,
contractAddress string,
querier *queries.Queries,
) (uint64, error) {
) (*Block, error) {
block := &Block{
number: atomic.Uint64{},
hash: common.Hash{},
}

latestBlock, err := querier.GetLatestBlock(ctx, contractAddress)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return 0, nil
return block, nil
}
return 0, err
return block, err
}

if latestBlock < 0 {
return 0, fmt.Errorf(
if latestBlock.BlockNumber < 0 {
return block, fmt.Errorf(
"invalid block number %d for contract %s",
latestBlock,
latestBlock.BlockNumber,
contractAddress,
)
}

return uint64(latestBlock), nil
block.number.Store(uint64(latestBlock.BlockNumber))
block.hash = common.BytesToHash(latestBlock.BlockHash)

return block, nil
}
Loading

0 comments on commit 235c2be

Please sign in to comment.