From e8d78b4fc9089e58d77ab41736b7847d47c9b3d7 Mon Sep 17 00:00:00 2001
From: Sean McGary <sean@seanmcgary.com>
Date: Fri, 6 Sep 2024 12:18:05 -0500
Subject: [PATCH 1/6] Remove unused tables

---
 internal/storage/postgresql/blockTables.go | 131 ---------
 internal/storage/postgresql/stateTables.go | 303 ---------------------
 2 files changed, 434 deletions(-)
 delete mode 100644 internal/storage/postgresql/blockTables.go
 delete mode 100644 internal/storage/postgresql/stateTables.go

diff --git a/internal/storage/postgresql/blockTables.go b/internal/storage/postgresql/blockTables.go
deleted file mode 100644
index d78e5a21..00000000
--- a/internal/storage/postgresql/blockTables.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package postgresql
-
-import "database/sql"
-
-func (p *PostgresBlockStore) CloneRegisteredAvsOperatorsForNewBlock(newBlockNumber uint64) error {
-	query := `
-		insert into registered_avs_operators (avs, operator, block_number)
-			select
-				operator,
-				avs,
-				@newBlockNumber as block_number
-			from registered_avs_operators where block_number = @previousBlockNumber
-	`
-	results := p.Db.Exec(query, sql.Named("newBlockNumber", newBlockNumber), sql.Named("previousBlockNumber", newBlockNumber-1))
-	if results.Error != nil {
-		return results.Error
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) CloneOperatorSharesForNewBlock(newBlockNumber uint64) error {
-	query := `
-		insert into operator_shares (operator, strategy, shares, block_number)
-			select
-				operator,
-				strategy,
-				shares,
-				@newBlockNumber as block_number
-			from operator_shares where block_number = @previousBlockNumber
-	`
-	results := p.Db.Exec(query, sql.Named("newBlockNumber", newBlockNumber), sql.Named("previousBlockNumber", newBlockNumber-1))
-	if results.Error != nil {
-		return results.Error
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) CloneStakerSharesForNewBlock(newBlockNumber uint64) error {
-	query := `
-		insert into staker_shares (staker, strategy, shares, block_number)
-			select
-				staker,
-				strategy,
-				shares,
-				@newBlockNumber as block_number
-			from staker_shares where block_number = @previousBlockNumber
-	`
-	results := p.Db.Exec(query, sql.Named("newBlockNumber", newBlockNumber), sql.Named("previousBlockNumber", newBlockNumber-1))
-	if results.Error != nil {
-		return results.Error
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) CloneDelegatedStakersForNewBlock(newBlockNumber uint64) error {
-	query := `
-		insert into delegated_stakers (staker, operator, block_number)
-			select
-				staker,
-				operator,
-				@blockNumber as block_number
-			from delegated_stakers where block_number = @previousBlockNumber
-	`
-	results := p.Db.Exec(query, sql.Named("newBlockNumber", newBlockNumber), sql.Named("previousBlockNumber", newBlockNumber-1))
-	if results.Error != nil {
-		return results.Error
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) SetActiveRewardsForNewBlock(newBlockNumber uint64) error {
-	// At the given block, we want to store all rewards that have not yet met their end_timestamp.
-	//
-	// Once end_timestamp < current_block.timestamp, the reward is no longer active and should not be included in the active_rewards table.
-	query := `
-		with current_block as (
-			select * from blocks where number = @newBlockNumber limit 1
-		),
-		insert into active_rewards (avs, reward_hash, token, amount, strategy, multiplier, strategy_index, block_number, start_timestamp, end_timestamp, duration)
-			select
-				avs,
-				reward_hash,
-				token,
-				amount,
-				strategy,
-				multiplier,
-				strategy_index,
-				@newBlockNumber as block_number,
-				start_timestamp,
-				end_timestamp,
-				duration
-			from active_reward_submissions
-			where end_timestamp > current_block.timestamp
-	`
-	results := p.Db.Exec(query, sql.Named("newBlockNumber", newBlockNumber))
-	if results.Error != nil {
-		return results.Error
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) SetActiveRewardForAllForNewBlock(newBlockNumber uint64) error {
-	// At the given block, we want to store all rewards that have not yet met their end_timestamp.
-	//
-	// Once end_timestamp < current_block.timestamp, the reward is no longer active and should not be included in the active_rewards table.
-	query := `
-		with current_block as (
-			select * from blocks where number = @newBlockNumber limit 1
-		),
-		insert into active_reward_for_all (avs, reward_hash, token, amount, strategy, multiplier, strategy_index, block_number, start_timestamp, end_timestamp, duration)
-			select
-				avs,
-				reward_hash,
-				token,
-				amount,
-				strategy,
-				multiplier,
-				strategy_index,
-				@newBlockNumber as block_number,
-				start_timestamp,
-				end_timestamp,
-				duration
-			from active_reward_for_all_submissions
-			where end_timestamp > current_block.timestamp
-	`
-	results := p.Db.Exec(query, sql.Named("newBlockNumber", newBlockNumber))
-	if results.Error != nil {
-		return results.Error
-	}
-	return nil
-}
diff --git a/internal/storage/postgresql/stateTables.go b/internal/storage/postgresql/stateTables.go
deleted file mode 100644
index edfbaabf..00000000
--- a/internal/storage/postgresql/stateTables.go
+++ /dev/null
@@ -1,303 +0,0 @@
-package postgresql
-
-import (
-	"database/sql"
-	"go.uber.org/zap"
-	"golang.org/x/xerrors"
-)
-
-func (p *PostgresBlockStore) InsertIntoAvsOperatorChangesForBlock(blockNumber uint64) error {
-	query := `
-		with avs_operator_changes as (
-			SELECT
-				lower(t.arguments #>> '{0,Value}') as operator,
-				lower(t.arguments #>> '{1,Value}') as avs,
-				(case when (t.output_data -> 'status')::int = 1 then true else false) as registered,
-				t.transaction_hash,
-				t.log_index,
-				t.block_number
-			FROM transaction_logs t
-			WHERE t.address = ?
-			AND t.event_name = 'OperatorAVSRegistrationStatusUpdated'
-			AND t.block_number = ?
-		)
-		select
-			*
-		into avs_operator_changes
-		from avs_operator_changes
-	`
-	addressMap := p.GlobalConfig.GetContractsMapForEnvAndNetwork()
-	if addressMap == nil {
-		p.Logger.Sugar().Error("Failed to get contracts map for env and network")
-		return xerrors.New("failed to get contracts map for env and network")
-	}
-	result := p.Db.Raw(query, addressMap.AvsDirectory, blockNumber)
-	if result.Error != nil {
-		p.Logger.Sugar().Errorw("Failed to insert into avs operator changes for block",
-			zap.Error(result.Error),
-			zap.Uint64("blockNumber", blockNumber),
-		)
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) InsertIntoOperatorShareChangesForBlock(blockNumber uint64) error {
-	query := `
-		with operator_share_changes as (
-			SELECT
-				lower(t.arguments #>> '{0,Value}') as operator,
-				lower(t.output_data ->> 'staker') as staker,
-				lower(t.output_data ->> 'strategy') as strategy,
-				case
-					(when t.event_name = 'OperatorSharesIncreased' then (t.output_data ->> 'shares')::numeric(78,0) 
-					else (t.output_data ->> 'shares')::numeric(78,0) * -1) as shares,
-				t.transaction_hash,
-				t.log_index,
-				t.block_number
-			FROM transaction_logs t
-			WHERE t.address = ?
-			AND t.event_name in('OperatorSharesIncreased', 'OperatorSharesDecreased')
-			AND t.block_number = ?
-		)
-		select
-		*
-		into operator_share_changes
-		from operator_share_changes
-	`
-	addressMap := p.GlobalConfig.GetContractsMapForEnvAndNetwork()
-	if addressMap == nil {
-		p.Logger.Sugar().Error("Failed to get contracts map for env and network")
-		return xerrors.New("failed to get contracts map for env and network")
-	}
-	result := p.Db.Raw(query, addressMap.DelegationManager, blockNumber)
-	if result.Error != nil {
-		p.Logger.Sugar().Errorw("Failed to insert into operator share changes for block",
-			zap.Error(result.Error),
-			zap.Uint64("blockNumber", blockNumber),
-		)
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) InsertIntoStakerShareChangesForBlock(blockNumber uint64) error {
-	query := `
-		with staker_deposits as (
-			SELECT
-				lower(coalesce(t.output_data ->> 'depositor', t.output_data ->> 'staker')) as staker,
-				lower(t.output_data ->> 'strategy') as strategy,
-				(t.output_data ->> 'shares')::numeric(78,0) as shares,
-				t.transaction_hash,
-				t.log_index,
-				t.block_number
-			FROM transaction_logs as t
-			WHERE t.address = @strategyManagerAddress
-			AND t.event_name 'Deposit'
-			AND t.block_number = @blockNumber
-		),
-		staker_m1_withdrawals as (
-			SELECT
-				lower(coalesce(t.output_data ->> 'depositor', t.output_data ->> 'staker')) as staker,
-				lower(t.output_data ->> 'strategy') as strategy,
-				(t.output_data ->> 'shares')::numeric(78,0) * -1 as shares,
-				t.transaction_hash,
-				t.log_index,
-				t.block_number
-			FROM transaction_logs t
-			WHERE t.address = @strategyManagerAddress
-			AND t.event_name 'ShareWithdrawalQueued'
-			AND t.block_number = @blockNumber
-			-- Remove this transaction hash as it is the only withdrawal on m1 that was completed as shares. There is no corresponding deposit event. The withdrawal was completed to the same staker address.
-			AND t.transaction_hash != '0x62eb0d0865b2636c74ed146e2d161e39e42b09bac7f86b8905fc7a830935dc1e'
-		),
-		staker_m2_withdrawals as (
-			WITH migrations AS (
-			  SELECT 
-				(
-				  SELECT lower(string_agg(lpad(to_hex(elem::int), 2, '0'), ''))
-				  FROM jsonb_array_elements_text(t.output_data->'oldWithdrawalRoot') AS elem
-				) AS m1_withdrawal_root,
-				(
-				  SELECT lower(string_agg(lpad(to_hex(elem::int), 2, '0'), ''))
-				  FROM jsonb_array_elements_text(t.output_data->'newWithdrawalRoot') AS elem
-				) AS m2_withdrawal_root
-			  FROM transaction_logs t
-			  WHERE t.address = @delegationManagerAddress
-			  AND t.event_name = 'WithdrawalMigrated'
-			),
-			full_m2_withdrawals AS (
-			  SELECT
-				lower(t.output_data #>> '{withdrawal}') as withdrawals,
-				(
-				  SELECT lower(string_agg(lpad(to_hex(elem::int), 2, '0'), ''))
-				  FROM jsonb_array_elements_text(t.output_data ->'withdrawalRoot') AS elem
-				) AS withdrawal_root,
-				lower(t.output_data #>> '{withdrawal, staker}') AS staker,
-				lower(t_strategy.strategy) AS strategy,
-				(t_share.share)::numeric(78,0) AS shares,
-				t_strategy.strategy_index,
-				t_share.share_index,
-				t.transaction_hash,
-				t.log_index,
-				t.block_number
-			  FROM transaction_logs t
-			  left join blocks as b on (t.block_sequence_id = b.id),
-			  	jsonb_array_elements_text(t.output_data #> '{withdrawal, strategies}') WITH ORDINALITY AS t_strategy(strategy, strategy_index),
-			  	jsonb_array_elements_text(t.output_data #> '{withdrawal, shares}') WITH ORDINALITY AS t_share(share, share_index)
-			  WHERE t.address = @delegationManagerAddress
-			  AND t.event_name = 'WithdrawalQueued'
-			  AND t_strategy.strategy_index = t_share.share_index
-			  AND t.block_number = @blockNumber
-			)
-			-- Parse out the m2 withdrawals that were migrated from m1
-			SELECT 
-			  full_m2_withdrawals.*
-			FROM 
-			  full_m2_withdrawals
-			LEFT JOIN 
-			  migrations 
-			ON 
-			  full_m2_withdrawals.withdrawal_root = migrations.m2_withdrawal_root
-			WHERE 
-			  migrations.m2_withdrawal_root IS NULL
-		),
-		eigenpod_shares as (
-			SELECT
-				lower(t.arguments #>> '{0,Value}') AS staker,
-				(t.output_data ->> 'sharesDelta')::numeric(78,0) as shares,
-				t.transaction_hash,
-				t.log_index,
-				t.block_number
-			FROM transaction_logs t
-			WHERE t.address = @eigenpodManagerAddress
-			AND t.event_name = 'PodSharesUpdated'
-			AND t.block_number = @blockNumber
-		)
-		combined_results as (
-			SELECT staker, strategy, shares, 0 as strategy_index, transaction_hash, log_index, block_number
-			FROM staker_deposits
-		
-			UNION ALL
-		
-			-- Subtract m1 & m2 withdrawals
-			SELECT staker, strategy, shares * -1, 0 as strategy_index, transaction_hash, log_index, block_number
-			FROM staker_m1_withdrawals
-		
-			UNION ALL
-		
-			SELECT staker, strategy, shares * -1, strategy_index, transaction_hash, log_index, block_number
-			FROM staker_m2_withdrawals
-		
-			UNION all
-		
-			-- Shares in eigenpod are positive or negative, so no need to multiply by -1
-			SELECT staker, '0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0' as strategy, shares, 0 as strategy_index, transaction_hash, log_index, block_number
-			FROM eigenpod_shares
-		)
-		select
-		*
-		into staker_share_changes
-		from combined_results
-	`
-	addressMap := p.GlobalConfig.GetContractsMapForEnvAndNetwork()
-	if addressMap == nil {
-		p.Logger.Sugar().Error("Failed to get contracts map for env and network")
-		return xerrors.New("failed to get contracts map for env and network")
-	}
-	result := p.Db.Raw(query,
-		sql.Named("strategyManagerAddress", addressMap.StrategyManager),
-		sql.Named("delegationManagerAddress", addressMap.DelegationManager),
-		sql.Named("eigenpodManagerAddress", addressMap.EigenpodManager),
-		sql.Named("blockNumber", blockNumber),
-	)
-	if result.Error != nil {
-		p.Logger.Sugar().Errorw("Failed to insert into staker share changes for block",
-			zap.Error(result.Error),
-			zap.Uint64("blockNumber", blockNumber),
-		)
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) InsertIntoStakerDelegationChangesForBlock(blockNumber uint64) error {
-	query := `
-		with staker_delegations as (
-			SELECT
-				lower(t.arguments #>> '{0,Value}') as staker,
-				lower(t.output_data ->> 'strategy') as strategy,
-				case when t.event_name = 'StakerDelegated' then true else false end as delegated,
-				t.transaction_hash,
-				t.log_index,
-				t.block_number
-			FROM transaction_logs t
-			WHERE t.address = ?
-			AND t.event_name in('StakerDelegated', 'StakerUndelegated')
-			AND t.block_number = ?
-		)
-		select
-		*
-		into staker_delegation_changes
-		from staker_delegations
-	`
-	addressMap := p.GlobalConfig.GetContractsMapForEnvAndNetwork()
-	if addressMap == nil {
-		p.Logger.Sugar().Error("Failed to get contracts map for env and network")
-		return xerrors.New("failed to get contracts map for env and network")
-	}
-	result := p.Db.Raw(query, addressMap.DelegationManager, blockNumber)
-	if result.Error != nil {
-		p.Logger.Sugar().Errorw("Failed to insert into staker share changes for block",
-			zap.Error(result.Error),
-			zap.Uint64("blockNumber", blockNumber),
-		)
-	}
-	return nil
-}
-
-func (p *PostgresBlockStore) InsertIntoActiveRewardSubmissionsForBlock(blockNumber uint64) error {
-	query := `
-		with rows_to_insert as (
-			SELECT
-				lower(tl.arguments #>> '{0,Value}') AS avs,
-				lower(tl.arguments #>> '{2,Value}') AS reward_hash,
-				coalesce(lower(tl.output_data #>> '{rewardsSubmission}'), lower(tl.output_data #>> '{rangePayment}')) as rewards_submission,
-				coalesce(lower(tl.output_data #>> '{rewardsSubmission, token}'), lower(tl.output_data #>> '{rangePayment, token}')) as token,
-				coalesce(tl.output_data #>> '{rewardsSubmission,amount}', tl.output_data #>> '{rangePayment,amount}')::numeric(78,0) as amount,
-				to_timestamp(coalesce(tl.output_data #>> '{rewardsSubmission,startTimestamp}', tl.output_data #>> '{rangePayment,startTimestamp}')::bigint)::timestamp(6) as start_timestamp,
-				coalesce(tl.output_data #>> '{rewardsSubmission,duration}', tl.output_data #>> '{rangePayment,duration}')::bigint as duration,
-				to_timestamp(
-						coalesce(tl.output_data #>> '{rewardsSubmission,startTimestamp}', tl.output_data #>> '{rangePayment,startTimestamp}')::bigint
-							+ coalesce(tl.output_data #>> '{rewardsSubmission,duration}', tl.output_data #>> '{rangePayment,duration}')::bigint
-				)::timestamp(6) as end_timestamp,
-				lower(t.entry ->> 'strategy') as strategy,
-				(t.entry ->> 'multiplier')::numeric(78,0) as multiplier,
-				t.strategy_index as strategy_index,
-				tl.transaction_hash,
-				tl.log_index,
-				tl.block_number
-			FROM transaction_logs tl
-				CROSS JOIN LATERAL jsonb_array_elements(
-					coalesce(tl.output_data #> '{rewardsSubmission,strategiesAndMultipliers}',tl.output_data #> '{rangePayment,strategiesAndMultipliers}')
-				) WITH ORDINALITY AS t(entry, strategy_index)
-			WHERE address = ?
-			AND (event_name = 'AVSRewardsSubmissionCreated' or event_name = 'RangePaymentCreated')
-			AND block_number = ?
-		)
-		select
-		*
-		into active_reward_submissions
-		from rows_to_insert
-	`
-	addressMap := p.GlobalConfig.GetContractsMapForEnvAndNetwork()
-	if addressMap == nil {
-		p.Logger.Sugar().Error("Failed to get contracts map for env and network")
-		return xerrors.New("failed to get contracts map for env and network")
-	}
-	result := p.Db.Raw(query, addressMap.RewardsCoordinator, blockNumber)
-	if result.Error != nil {
-		p.Logger.Sugar().Errorw("Failed to insert into avs operator changes for block",
-			zap.Error(result.Error),
-			zap.Uint64("blockNumber", blockNumber),
-		)
-	}
-	return nil
-}

From 2ab3318d5fbd61a657b91d7f9fe81e34ac0feaed Mon Sep 17 00:00:00 2001
From: Sean McGary <sean@seanmcgary.com>
Date: Fri, 6 Sep 2024 12:33:48 -0500
Subject: [PATCH 2/6] Implement the storage interface for sqlite

---
 internal/storage/sqlite/storage.go | 171 +++++++++++++++++++++++++++--
 1 file changed, 164 insertions(+), 7 deletions(-)

diff --git a/internal/storage/sqlite/storage.go b/internal/storage/sqlite/storage.go
index afa28fed..67d901cc 100644
--- a/internal/storage/sqlite/storage.go
+++ b/internal/storage/sqlite/storage.go
@@ -1,9 +1,18 @@
 package sqlite
 
 import (
+	"encoding/json"
+	"errors"
+	"fmt"
 	"github.com/Layr-Labs/sidecar/internal/config"
+	"github.com/Layr-Labs/sidecar/internal/parser"
+	"github.com/Layr-Labs/sidecar/internal/storage"
 	"go.uber.org/zap"
+	"golang.org/x/xerrors"
 	"gorm.io/gorm"
+	"gorm.io/gorm/clause"
+	"strings"
+	"time"
 )
 
 type SqliteBlockStoreConfig struct {
@@ -26,22 +35,170 @@ func NewSqliteBlockStore(db *gorm.DB, l *zap.Logger, cfg *config.Config) *Sqlite
 	return bs
 }
 
-/*
-func (s *SqliteBlockStore) InsertBlockAtHeight(blockNumber uint64, hash string, blockTime uint64) (*storage.Block, error) {
+func (s *SqliteBlockStore) InsertBlockAtHeight(
+	blockNumber uint64,
+	hash string,
+	blockTime uint64,
+) (*storage.Block, error) {
+	block := &storage.Block{
+		Number:    blockNumber,
+		Hash:      hash,
+		BlockTime: time.Unix(int64(blockTime), 0),
+	}
+
+	res := s.Db.Model(&storage.Block{}).Clauses(clause.Returning{}).Create(&block)
 
+	if res.Error != nil {
+		return nil, fmt.Errorf("failed to insert block with number '%d': %w", blockNumber, res.Error)
+	}
+	return block, nil
 }
-func (s *SqliteBlockStore) InsertBlockTransaction(blockNumber uint64, txHash string, txIndex uint64, from string, to string, contractAddress string, bytecodeHash string) (*storage.Transaction, error) {
 
+func (s *SqliteBlockStore) InsertBlockTransaction(
+	blockNumber uint64,
+	txHash string,
+	txIndex uint64,
+	from string,
+	to string,
+	contractAddress string,
+	bytecodeHash string,
+) (*storage.Transaction, error) {
+	to = strings.ToLower(to)
+	from = strings.ToLower(from)
+	contractAddress = strings.ToLower(contractAddress)
+
+	tx := &storage.Transaction{
+		BlockNumber:      blockNumber,
+		TransactionHash:  txHash,
+		TransactionIndex: txIndex,
+		FromAddress:      from,
+		ToAddress:        to,
+		ContractAddress:  contractAddress,
+		BytecodeHash:     bytecodeHash,
+	}
+
+	result := s.Db.Model(&storage.Transaction{}).Clauses(clause.Returning{}).Create(&tx)
+
+	if result.Error != nil {
+		return nil, xerrors.Errorf("Failed to insert block transaction '%d' - '%s': %w", blockNumber, txHash, result.Error)
+	}
+	return tx, nil
 }
-func (s *SqliteBlockStore) InsertTransactionLog(txHash string, transactionIndex uint64, blockNumber uint64, log *parser.DecodedLog, outputData map[string]interface{}) (*storage.Transaction, error) {
 
+func (s *SqliteBlockStore) InsertTransactionLog(
+	txHash string,
+	transactionIndex uint64,
+	blockNumber uint64,
+	log *parser.DecodedLog,
+	outputData map[string]interface{},
+) (*storage.TransactionLog, error) {
+	argsJson, err := json.Marshal(log.Arguments)
+	if err != nil {
+		s.Logger.Sugar().Errorw("Failed to marshal arguments", zap.Error(err))
+	}
+
+	outputDataJson := []byte{}
+	outputDataJson, err = json.Marshal(outputData)
+	if err != nil {
+		s.Logger.Sugar().Errorw("Failed to marshal output data", zap.Error(err))
+	}
+
+	txLog := &storage.TransactionLog{
+		TransactionHash:  txHash,
+		TransactionIndex: transactionIndex,
+		BlockNumber:      blockNumber,
+		Address:          strings.ToLower(log.Address),
+		Arguments:        string(argsJson),
+		EventName:        log.EventName,
+		LogIndex:         log.LogIndex,
+		OutputData:       string(outputDataJson),
+	}
+	result := s.Db.Model(&storage.TransactionLog{}).Clauses(clause.Returning{}).Create(&txLog)
+
+	if result.Error != nil {
+		return nil, xerrors.Errorf("Failed to insert transaction log: %w - %+v", result.Error, txLog)
+	}
+	return txLog, nil
 }
+
+type latestBlockNumber struct {
+	BlockNumber uint64
+}
+
 func (s *SqliteBlockStore) GetLatestBlock() (int64, error) {
+	block := &latestBlockNumber{}
 
+	query := `select coalesce(max(number), 0) as block_number from blocks`
+
+	result := s.Db.Raw(query).Scan(&block)
+	if result.Error != nil {
+		return 0, xerrors.Errorf("Failed to get latest block: %w", result.Error)
+	}
+	return int64(block.BlockNumber), nil
 }
-func (s *SqliteBlockStore) GetBlockByNumber(blockNumber uint64) (*Block, error) {
 
+func (s *SqliteBlockStore) GetBlockByNumber(blockNumber uint64) (*storage.Block, error) {
+	block := &storage.Block{}
+
+	result := s.Db.Model(block).Where("number = ?", blockNumber).First(&block)
+	if result.Error != nil {
+		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
+			return nil, nil
+		}
+		return nil, result.Error
+	}
+	return block, nil
 }
-func (s *SqliteBlockStore) InsertOperatorRestakedStrategies(avsDirectorAddress string, blockNumber uint64, blockTime time.Time, operator string, avs string, strategy string) (*OperatorRestakedStrategies, error) {
 
-}*/
+func (s *SqliteBlockStore) InsertOperatorRestakedStrategies(
+	avsDirectorAddress string,
+	blockNumber uint64,
+	blockTime time.Time,
+	operator string,
+	avs string,
+	strategy string,
+) (*storage.OperatorRestakedStrategies, error) {
+	ors := &storage.OperatorRestakedStrategies{
+		AvsDirectoryAddress: strings.ToLower(avsDirectorAddress),
+		BlockNumber:         blockNumber,
+		Operator:            operator,
+		Avs:                 avs,
+		Strategy:            strategy,
+		BlockTime:           blockTime,
+	}
+
+	result := s.Db.Model(&storage.OperatorRestakedStrategies{}).Clauses(clause.Returning{}).Create(&ors)
+
+	if result.Error != nil {
+		return nil, xerrors.Errorf("Failed to insert operator restaked strategies: %w", result.Error)
+	}
+	return ors, nil
+}
+
+func (s *SqliteBlockStore) GetLatestActiveAvsOperators(blockNumber uint64, avsDirectoryAddress string) ([]*storage.ActiveAvsOperator, error) {
+	avsDirectoryAddress = strings.ToLower(avsDirectoryAddress)
+
+	rows := make([]*storage.ActiveAvsOperator, 0)
+	query := `
+		WITH latest_status AS (
+			SELECT 
+				lower(tl.arguments #>> '{0,Value}') as operator,
+				lower(tl.arguments #>> '{1,Value}') as avs,
+				lower(tl.output_data #>> '{status}') as status,
+				ROW_NUMBER() OVER (PARTITION BY lower(tl.arguments #>> '{0,Value}'), lower(tl.arguments #>> '{1,Value}') ORDER BY block_number DESC) AS row_number
+			FROM transaction_logs as tl
+			WHERE
+				tl.address = ?
+				AND tl.event_name = 'OperatorAVSRegistrationStatusUpdated'
+				AND tl.block_number <= ?
+		)
+		SELECT avs, operator
+		FROM latest_status
+		WHERE row_number = 1 AND status = '1';
+	`
+	result := s.Db.Raw(query, avsDirectoryAddress, blockNumber).Scan(&rows)
+	if result.Error != nil {
+		return nil, xerrors.Errorf("Failed to get latest active AVS operators: %w", result.Error)
+	}
+	return rows, nil
+}

From 398f85b85b228f1a21bc4c72b8e755f8a4c0b7b1 Mon Sep 17 00:00:00 2001
From: Sean McGary <sean@seanmcgary.com>
Date: Fri, 6 Sep 2024 13:38:51 -0500
Subject: [PATCH 3/6] Setup sqlite with migrations

---
 .../migrations/202409061249_bootstrapDb/up.go |  97 ++++++++++++++++
 internal/sqlite/sqlite.go                     |  18 ++-
 .../storage/sqlite/migrations/migrator.go     | 108 ++++++++++++++++++
 internal/storage/sqlite/sqlite_test.go        |  83 ++++++++++++++
 internal/storage/storage.go                   |   2 -
 internal/tests/utils.go                       |  11 ++
 scripts/generateSqliteMigration.sh            |  38 ++++++
 7 files changed, 354 insertions(+), 3 deletions(-)
 create mode 100644 internal/sqlite/migrations/202409061249_bootstrapDb/up.go
 create mode 100644 internal/storage/sqlite/migrations/migrator.go
 create mode 100644 internal/storage/sqlite/sqlite_test.go
 create mode 100755 scripts/generateSqliteMigration.sh

diff --git a/internal/sqlite/migrations/202409061249_bootstrapDb/up.go b/internal/sqlite/migrations/202409061249_bootstrapDb/up.go
new file mode 100644
index 00000000..09ac2d31
--- /dev/null
+++ b/internal/sqlite/migrations/202409061249_bootstrapDb/up.go
@@ -0,0 +1,97 @@
+package _202409061249_bootstrapDb
+
+import (
+	"fmt"
+	"gorm.io/gorm"
+)
+
+type SqliteMigration struct {
+}
+
+func (m *SqliteMigration) Up(grm *gorm.DB) error {
+	queries := []string{
+		`CREATE TABLE IF NOT EXISTS blocks (
+			number INTEGER NOT NULL PRIMARY KEY,
+			hash text NOT NULL,
+			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+			block_time DATETIME NOT NULL,
+			updated_at DATETIME DEFAULT NULL,
+			deleted_at DATETIME DEFAULT NULL
+    	)`,
+		`CREATE TABLE IF NOT EXISTS transactions (
+			block_number INTEGER NOT NULL REFERENCES blocks(number) ON DELETE CASCADE,
+			transaction_hash TEXT NOT NULL,
+			transaction_index INTEGER NOT NULL,
+			from_address TEXT NOT NULL,
+			to_address TEXT DEFAULT NULL,
+			contract_address TEXT DEFAULT NULL,
+			bytecode_hash TEXT DEFAULT NULL,
+			gas_used INTEGER DEFAULT NULL,
+			cumulative_gas_used INTEGER DEFAULT NULL,
+			effective_gas_price INTEGER DEFAULT NULL,
+			created_at DATETIME DEFAULT current_timestamp,
+			updated_at DATETIME DEFAULT NULL,
+			deleted_at DATETIME DEFAULT NULL,
+			UNIQUE(block_number, transaction_hash, transaction_index)
+		)`,
+		`CREATE TABLE IF NOT EXISTS transaction_logs (
+			transaction_hash TEXT NOT NULL REFERENCES transactions(transaction_hash) ON DELETE CASCADE,
+			address TEXT NOT NULL,
+			arguments TEXT,
+			event_name TEXT NOT NULL,
+			log_index INTEGER NOT NULL,
+			block_number INTEGER NOT NULL REFERENCES blocks(number) ON DELETE CASCADE,
+			transaction_index INTEGER NOT NULL,
+			output_data TEXT
+			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+			updated_at DATETIME zone,
+			deleted_at DATETIME zone,
+			UNIQUE(transaction_hash, log_index)
+    	)`,
+		`CREATE TABLE IF NOT EXISTS contracts (
+			contract_address TEXT NOT NULL,
+			contract_abi TEXT,
+			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+			updated_at DATETIME,
+			deleted_at DATETIME,
+			bytecode_hash TEXT DEFAULT NULL,
+			verified INTEGER DEFAULT false,
+			matching_contract_address TEXT DEFAULT NULL,
+			checked_for_proxy INTEGER DEFAULT 0 NOT NULL,
+			checked_for_abi INTEGER NOT NULL,
+			UNIQUE(contract_address)
+		)`,
+		`CREATE TABLE IF NOT EXISTS proxy_contracts (
+			block_number INTEGER NOT NULL,
+			contract_address TEXT NOT NULL PRIMARY KEY REFERENCES contracts(contract_address) ON DELETE CASCADE,
+			proxy_contract_address TEXT NOT NULL REFERENCES contracts(contract_address) ON DELETE CASCADE,
+			created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
+			updated_at DATETIME,
+			deleted_at DATETIME
+		)`,
+		`CREATE TABLE IF NOT EXISTS operator_restaked_strategies (
+			block_number INTEGER NOT NULL REFERENCES blocks(number) ON DELETE CASCADE,
+			operator TEXT NOT NULL,
+			avs TEXT NOT NULL,
+			strategy TEXT NOT NULL,
+			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+			updated_at DATETIME,
+			deleted_at DATETIME,
+			block_time DATETIME NOT NULL,
+			avs_directory_address TEXT
+		);`,
+	}
+
+	for _, query := range queries {
+		res := grm.Exec(query)
+		if res.Error != nil {
+			fmt.Printf("Failed to run migration query: %s - %+v\n", query, res.Error)
+			return res.Error
+		}
+	}
+	return nil
+}
+
+func (m *SqliteMigration) GetName() string {
+	return "202409061249_bootstrapDb"
+}
diff --git a/internal/sqlite/sqlite.go b/internal/sqlite/sqlite.go
index 4837f601..8ad06264 100644
--- a/internal/sqlite/sqlite.go
+++ b/internal/sqlite/sqlite.go
@@ -11,5 +11,21 @@ func NewSqlite(path string) gorm.Dialector {
 }
 
 func NewGormSqliteFromSqlite(sqlite gorm.Dialector) (*gorm.DB, error) {
-	return gorm.Open(sqlite, &gorm.Config{})
+	db, err := gorm.Open(sqlite, &gorm.Config{})
+	if err != nil {
+		return nil, err
+	}
+
+	pragmas := []string{
+		`PRAGMA foreign_keys = ON;`,
+		`PRAGMA journal_mode = WAL;`,
+	}
+
+	for _, pragma := range pragmas {
+		res := db.Exec(pragma)
+		if res.Error != nil {
+			return nil, res.Error
+		}
+	}
+	return db, nil
 }
diff --git a/internal/storage/sqlite/migrations/migrator.go b/internal/storage/sqlite/migrations/migrator.go
new file mode 100644
index 00000000..366e9f33
--- /dev/null
+++ b/internal/storage/sqlite/migrations/migrator.go
@@ -0,0 +1,108 @@
+package migrations
+
+import (
+	"database/sql"
+	"fmt"
+	_202409061249_bootstrapDb "github.com/Layr-Labs/sidecar/internal/sqlite/migrations/202409061249_bootstrapDb"
+	"go.uber.org/zap"
+	"gorm.io/gorm"
+	"time"
+)
+
+type ISqliteMigration interface {
+	Up(grm *gorm.DB) error
+	GetName() string
+}
+
+type SqliteMigrator struct {
+	Db     *sql.DB
+	GDb    *gorm.DB
+	Logger *zap.Logger
+}
+
+func NewSqliteMigrator(gDb *gorm.DB, l *zap.Logger) *SqliteMigrator {
+	return &SqliteMigrator{
+		GDb:    gDb,
+		Logger: l,
+	}
+}
+
+func (m *SqliteMigrator) MigrateAll() error {
+	err := m.CreateMigrationTablesIfNotExists()
+	if err != nil {
+		return err
+	}
+
+	migrations := []ISqliteMigration{
+		&_202409061249_bootstrapDb.SqliteMigration{},
+	}
+
+	for _, migration := range migrations {
+		err := m.Migrate(migration)
+		if err != nil {
+			panic(err)
+		}
+	}
+	return nil
+}
+
+func (m *SqliteMigrator) CreateMigrationTablesIfNotExists() error {
+	queries := []string{
+		`CREATE TABLE IF NOT EXISTS migrations (
+			name TEXT PRIMARY KEY,
+			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+			updated_at DATETIME DEFAULT NULL,
+			deleted_at DATETIME DEFAULT NULL
+		)`,
+	}
+
+	for _, query := range queries {
+		res := m.GDb.Exec(query)
+		if res.Error != nil {
+			m.Logger.Sugar().Errorw("Failed to create migration table", zap.Error(res.Error))
+			return res.Error
+		}
+	}
+	return nil
+}
+
+func (m *SqliteMigrator) Migrate(migration ISqliteMigration) error {
+	name := migration.GetName()
+
+	// find migration by name
+	var migrationRecord Migrations
+	result := m.GDb.Find(&migrationRecord, "name = ?", name).Limit(1)
+
+	if result.Error == nil && result.RowsAffected == 0 {
+		m.Logger.Sugar().Infof("Running migration '%s'", name)
+		// run migration
+		err := migration.Up(m.GDb)
+		if err != nil {
+			m.Logger.Sugar().Errorw(fmt.Sprintf("Failed to run migration '%s'", name), zap.Error(err))
+			return err
+		}
+
+		// record migration
+		migrationRecord = Migrations{
+			Name: name,
+		}
+		result = m.GDb.Create(&migrationRecord)
+		if result.Error != nil {
+			m.Logger.Sugar().Errorw(fmt.Sprintf("Failed to record migration '%s'", name), zap.Error(result.Error))
+			return result.Error
+		}
+	} else if result.Error != nil {
+		m.Logger.Sugar().Errorw(fmt.Sprintf("Failed to find migration '%s'", name), zap.Error(result.Error))
+		return result.Error
+	} else if result.RowsAffected > 0 {
+		m.Logger.Sugar().Infof("Migration %s already run", name)
+		return nil
+	}
+	return nil
+}
+
+type Migrations struct {
+	Name      string `gorm:"primaryKey"`
+	CreatedAt time.Time
+	UpdatedAt time.Time
+}
diff --git a/internal/storage/sqlite/sqlite_test.go b/internal/storage/sqlite/sqlite_test.go
new file mode 100644
index 00000000..2df00d11
--- /dev/null
+++ b/internal/storage/sqlite/sqlite_test.go
@@ -0,0 +1,83 @@
+package sqlite
+
+import (
+	"fmt"
+	"github.com/Layr-Labs/sidecar/internal/config"
+	"github.com/Layr-Labs/sidecar/internal/logger"
+	"github.com/Layr-Labs/sidecar/internal/storage"
+	"github.com/Layr-Labs/sidecar/internal/storage/sqlite/migrations"
+	"github.com/Layr-Labs/sidecar/internal/tests"
+	"github.com/stretchr/testify/assert"
+	"go.uber.org/zap"
+	"gorm.io/gorm"
+	"testing"
+	"time"
+)
+
+func setup() (*gorm.DB, *zap.Logger, *config.Config) {
+	cfg := config.NewConfig()
+	l, err := logger.NewLogger(&logger.LoggerConfig{Debug: true})
+	db, err := tests.GetSqliteDatabaseConnection()
+	if err != nil {
+		panic(err)
+	}
+	sqliteMigrator := migrations.NewSqliteMigrator(db, l)
+	if err := sqliteMigrator.MigrateAll(); err != nil {
+		l.Sugar().Fatalw("Failed to migrate", "error", err)
+	}
+	return db, l, cfg
+}
+
+func teardown(db *gorm.DB, l *zap.Logger) {
+	queries := []string{
+		`truncate table blocks cascade`,
+		`truncate table transactions cascade`,
+		`truncate table transaction_logs cascade`,
+		`truncate table transaction_logs cascade`,
+	}
+	for _, query := range queries {
+		res := db.Exec(query)
+		if res.Error != nil {
+			l.Sugar().Errorw("Failed to truncate table", "error", res.Error)
+		}
+	}
+}
+
+func Test_SqliteBlockstore(t *testing.T) {
+	t.Run("Blocks", func(t *testing.T) {
+		db, l, cfg := setup()
+
+		sqliteStore := NewSqliteBlockStore(db, l, cfg)
+
+		t.Run("InsertBlockAtHeight", func(t *testing.T) {
+			block := &storage.Block{
+				Number:    100,
+				Hash:      "some hash",
+				BlockTime: time.Now(),
+			}
+
+			insertedBlock, err := sqliteStore.InsertBlockAtHeight(block.Number, block.Hash, uint64(block.BlockTime.Unix()))
+			if err != nil {
+				t.Errorf("Failed to insert block: %v", err)
+			}
+			assert.NotNil(t, insertedBlock)
+			assert.Equal(t, block.Number, insertedBlock.Number)
+			assert.Equal(t, block.Hash, insertedBlock.Hash)
+		})
+		t.Run("Fail to insert a duplicate block", func(t *testing.T) {
+			block := &storage.Block{
+				Number:    100,
+				Hash:      "some hash",
+				BlockTime: time.Now(),
+			}
+
+			_, err := sqliteStore.InsertBlockAtHeight(block.Number, block.Hash, uint64(block.BlockTime.Unix()))
+			assert.NotNil(t, err)
+			assert.Contains(t, err.Error(), "UNIQUE constraint failed")
+			fmt.Printf("Error: %v\n", err)
+		})
+		t.Run("InsertBlockTransaction", func(t *testing.T) {
+
+		})
+	})
+}
diff --git a/internal/storage/storage.go b/internal/storage/storage.go
index 6f317a35..0ce24f51 100644
--- a/internal/storage/storage.go
+++ b/internal/storage/storage.go
@@ -19,11 +19,9 @@ type BlockStore interface {
 
 // Tables
 type Block struct {
-	Id        uint64 `gorm:"type:serial"`
 	Number    uint64
 	Hash      string
 	BlockTime time.Time
-	BlobPath  string
 	CreatedAt time.Time
 	UpdatedAt time.Time
 	DeletedAt time.Time
diff --git a/internal/tests/utils.go b/internal/tests/utils.go
index 14dfe528..f0a0d1d5 100644
--- a/internal/tests/utils.go
+++ b/internal/tests/utils.go
@@ -3,6 +3,7 @@ package tests
 import (
 	"github.com/Layr-Labs/sidecar/internal/config"
 	"github.com/Layr-Labs/sidecar/internal/postgres"
+	sqlite2 "github.com/Layr-Labs/sidecar/internal/sqlite"
 	"gorm.io/gorm"
 )
 
@@ -28,3 +29,13 @@ func GetDatabaseConnection(cfg *config.Config) (*postgres.Postgres, *gorm.DB, er
 	}
 	return db, grm, nil
 }
+
+const sqliteInMemoryPath = "file::memory:?cache=shared"
+
+func GetSqliteDatabaseConnection() (*gorm.DB, error) {
+	db, err := sqlite2.NewGormSqliteFromSqlite(sqlite2.NewSqlite(sqliteInMemoryPath))
+	if err != nil {
+		panic(err)
+	}
+	return db, nil
+}
diff --git a/scripts/generateSqliteMigration.sh b/scripts/generateSqliteMigration.sh
new file mode 100755
index 00000000..33a3061c
--- /dev/null
+++ b/scripts/generateSqliteMigration.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+name=$1
+
+if [[ -z $name ]]; then
+    echo "Usage: $0 <migration_name>"
+    exit 1
+fi
+
+timestamp=$(date +"%Y%m%d%H%M")
+
+migration_name="${timestamp}_${name}"
+
+migrations_dir="./internal/sqlite/migrations/${migration_name}"
+migration_file="${migrations_dir}/up.go"
+
+mkdir -p $migrations_dir || true
+
+# heredoc that creates a migration go file with an Up function
+cat > $migration_file <<EOF
+package _${timestamp}_${name}
+
+import (
+	"database/sql"
+	"gorm.io/gorm"
+)
+
+type SqliteMigration struct {
+}
+
+func (m *SqliteMigration) Up(db *sql.DB, grm *gorm.DB) error {
+	return nil
+}
+
+func (m *SqliteMigration) GetName() string {
+	return "${timestamp}_${name}"
+}
+EOF

From bcdfcb2a8e31e0d7e648694990b0608f220b4b5c Mon Sep 17 00:00:00 2001
From: Sean McGary <sean@seanmcgary.com>
Date: Fri, 6 Sep 2024 15:03:02 -0500
Subject: [PATCH 4/6] Test transaction insertion

---
 .../migrations/202409061249_bootstrapDb/up.go |  2 +-
 internal/storage/sqlite/sqlite_test.go        | 69 ++++++++++++++++++-
 2 files changed, 67 insertions(+), 4 deletions(-)

diff --git a/internal/sqlite/migrations/202409061249_bootstrapDb/up.go b/internal/sqlite/migrations/202409061249_bootstrapDb/up.go
index 09ac2d31..4d968baf 100644
--- a/internal/sqlite/migrations/202409061249_bootstrapDb/up.go
+++ b/internal/sqlite/migrations/202409061249_bootstrapDb/up.go
@@ -20,7 +20,7 @@ func (m *SqliteMigration) Up(grm *gorm.DB) error {
     	)`,
 		`CREATE TABLE IF NOT EXISTS transactions (
 			block_number INTEGER NOT NULL REFERENCES blocks(number) ON DELETE CASCADE,
-			transaction_hash TEXT NOT NULL,
+			transaction_hash TEXT NOT NULL PRIMARY KEY,
 			transaction_index INTEGER NOT NULL,
 			from_address TEXT NOT NULL,
 			to_address TEXT DEFAULT NULL,
diff --git a/internal/storage/sqlite/sqlite_test.go b/internal/storage/sqlite/sqlite_test.go
index 2df00d11..71d0e5fa 100644
--- a/internal/storage/sqlite/sqlite_test.go
+++ b/internal/storage/sqlite/sqlite_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"go.uber.org/zap"
 	"gorm.io/gorm"
+	"strings"
 	"testing"
 	"time"
 )
@@ -44,10 +45,13 @@ func teardown(db *gorm.DB, l *zap.Logger) {
 }
 
 func Test_SqliteBlockstore(t *testing.T) {
-	t.Run("Blocks", func(t *testing.T) {
-		db, l, cfg := setup()
+	db, l, cfg := setup()
+	sqliteStore := NewSqliteBlockStore(db, l, cfg)
+
+	insertedBlocks := make([]*storage.Block, 0)
+	insertedTransactions := make([]*storage.Transaction, 0)
 
-		sqliteStore := NewSqliteBlockStore(db, l, cfg)
+	t.Run("Blocks", func(t *testing.T) {
 
 		t.Run("InsertBlockAtHeight", func(t *testing.T) {
 			block := &storage.Block{
@@ -63,6 +67,8 @@ func Test_SqliteBlockstore(t *testing.T) {
 			assert.NotNil(t, insertedBlock)
 			assert.Equal(t, block.Number, insertedBlock.Number)
 			assert.Equal(t, block.Hash, insertedBlock.Hash)
+
+			insertedBlocks = append(insertedBlocks, insertedBlock)
 		})
 		t.Run("Fail to insert a duplicate block", func(t *testing.T) {
 			block := &storage.Block{
@@ -76,8 +82,65 @@ func Test_SqliteBlockstore(t *testing.T) {
 			assert.Contains(t, err.Error(), "UNIQUE constraint failed")
 			fmt.Printf("Error: %v\n", err)
 		})
+	})
+	t.Run("Transactions", func(t *testing.T) {
+		block := insertedBlocks[0]
+
 		t.Run("InsertBlockTransaction", func(t *testing.T) {
+			tx := storage.Transaction{
+				BlockNumber:      block.Number,
+				TransactionHash:  "txHash",
+				TransactionIndex: 0,
+				FromAddress:      "from",
+				ToAddress:        "to",
+				ContractAddress:  "contractAddress",
+				BytecodeHash:     "bytecodeHash",
+			}
+			insertedTx, err := sqliteStore.InsertBlockTransaction(
+				tx.BlockNumber,
+				tx.TransactionHash,
+				tx.TransactionIndex,
+				tx.FromAddress,
+				tx.ToAddress,
+				tx.ContractAddress,
+				tx.BytecodeHash,
+			)
+			assert.Nil(t, err)
+			assert.NotNil(t, insertedTx)
+			assert.Equal(t, tx.BlockNumber, insertedTx.BlockNumber)
+			assert.Equal(t, tx.TransactionHash, insertedTx.TransactionHash)
+			assert.Equal(t, tx.TransactionIndex, insertedTx.TransactionIndex)
+			assert.Equal(t, tx.FromAddress, insertedTx.FromAddress)
+			assert.Equal(t, tx.ToAddress, insertedTx.ToAddress)
+			assert.Equal(t, strings.ToLower(tx.ContractAddress), insertedTx.ContractAddress)
+			assert.Equal(t, tx.BytecodeHash, insertedTx.BytecodeHash)
 
+			insertedTransactions = append(insertedTransactions, insertedTx)
 		})
+		t.Run("Fail to insert a duplicate transaction", func(t *testing.T) {
+			tx := storage.Transaction{
+				BlockNumber:      block.Number,
+				TransactionHash:  "txHash",
+				TransactionIndex: 0,
+				FromAddress:      "from",
+				ToAddress:        "to",
+				ContractAddress:  "contractAddress",
+				BytecodeHash:     "bytecodeHash",
+			}
+			_, err := sqliteStore.InsertBlockTransaction(
+				tx.BlockNumber,
+				tx.TransactionHash,
+				tx.TransactionIndex,
+				tx.FromAddress,
+				tx.ToAddress,
+				tx.ContractAddress,
+				tx.BytecodeHash,
+			)
+			assert.NotNil(t, err)
+			assert.Contains(t, err.Error(), "UNIQUE constraint failed")
+		})
+	})
+	t.Run("TransactionLogs", func(t *testing.T) {
+
 	})
 }

From b61be160ad7dd58dd28fa1e281e0aa9f5b49602a Mon Sep 17 00:00:00 2001
From: Sean McGary <sean@seanmcgary.com>
Date: Fri, 6 Sep 2024 15:13:06 -0500
Subject: [PATCH 5/6] Test TransactionLog insertion

---
 .../migrations/202409061249_bootstrapDb/up.go |  6 +-
 internal/storage/sqlite/sqlite_test.go        | 80 +++++++++++++++++++
 2 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/internal/sqlite/migrations/202409061249_bootstrapDb/up.go b/internal/sqlite/migrations/202409061249_bootstrapDb/up.go
index 4d968baf..8e289481 100644
--- a/internal/sqlite/migrations/202409061249_bootstrapDb/up.go
+++ b/internal/sqlite/migrations/202409061249_bootstrapDb/up.go
@@ -42,10 +42,10 @@ func (m *SqliteMigration) Up(grm *gorm.DB) error {
 			log_index INTEGER NOT NULL,
 			block_number INTEGER NOT NULL REFERENCES blocks(number) ON DELETE CASCADE,
 			transaction_index INTEGER NOT NULL,
-			output_data TEXT
+			output_data TEXT,
 			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-			updated_at DATETIME zone,
-			deleted_at DATETIME zone,
+			updated_at DATETIME,
+			deleted_at DATETIME,
 			UNIQUE(transaction_hash, log_index)
     	)`,
 		`CREATE TABLE IF NOT EXISTS contracts (
diff --git a/internal/storage/sqlite/sqlite_test.go b/internal/storage/sqlite/sqlite_test.go
index 71d0e5fa..fd0af96a 100644
--- a/internal/storage/sqlite/sqlite_test.go
+++ b/internal/storage/sqlite/sqlite_test.go
@@ -1,9 +1,11 @@
 package sqlite
 
 import (
+	"encoding/json"
 	"fmt"
 	"github.com/Layr-Labs/sidecar/internal/config"
 	"github.com/Layr-Labs/sidecar/internal/logger"
+	"github.com/Layr-Labs/sidecar/internal/parser"
 	"github.com/Layr-Labs/sidecar/internal/storage"
 	"github.com/Layr-Labs/sidecar/internal/storage/sqlite/migrations"
 	"github.com/Layr-Labs/sidecar/internal/tests"
@@ -141,6 +143,84 @@ func Test_SqliteBlockstore(t *testing.T) {
 		})
 	})
 	t.Run("TransactionLogs", func(t *testing.T) {
+		t.Run("InsertTransactionLog", func(t *testing.T) {
+			decodedLog := &parser.DecodedLog{
+				LogIndex: 0,
+				Address:  "log-address",
+				Arguments: []parser.Argument{
+					parser.Argument{
+						Name:    "arg1",
+						Type:    "string",
+						Value:   "some-value",
+						Indexed: true,
+					},
+				},
+				EventName: "SomeEvent",
+				OutputData: map[string]interface{}{
+					"output": "data",
+				},
+			}
+
+			jsonArguments, _ := json.Marshal(decodedLog.Arguments)
+			jsonOutputData, _ := json.Marshal(decodedLog.OutputData)
+
+			txLog := &storage.TransactionLog{
+				TransactionHash:  insertedTransactions[0].TransactionHash,
+				TransactionIndex: insertedTransactions[0].TransactionIndex,
+				BlockNumber:      insertedTransactions[0].BlockNumber,
+			}
+
+			insertedTxLog, err := sqliteStore.InsertTransactionLog(
+				txLog.TransactionHash,
+				txLog.TransactionIndex,
+				txLog.BlockNumber,
+				decodedLog,
+				decodedLog.OutputData,
+			)
+			assert.Nil(t, err)
+
+			assert.Equal(t, txLog.TransactionHash, insertedTxLog.TransactionHash)
+			assert.Equal(t, txLog.TransactionIndex, insertedTxLog.TransactionIndex)
+			assert.Equal(t, txLog.BlockNumber, insertedTxLog.BlockNumber)
+			assert.Equal(t, decodedLog.Address, insertedTxLog.Address)
+			assert.Equal(t, decodedLog.EventName, insertedTxLog.EventName)
+			assert.Equal(t, decodedLog.LogIndex, insertedTxLog.LogIndex)
+			assert.Equal(t, string(jsonArguments), insertedTxLog.Arguments)
+			assert.Equal(t, string(jsonOutputData), insertedTxLog.OutputData)
+		})
+		t.Run("Fail to insert a duplicate transaction log", func(t *testing.T) {
+			decodedLog := &parser.DecodedLog{
+				LogIndex: 0,
+				Address:  "log-address",
+				Arguments: []parser.Argument{
+					parser.Argument{
+						Name:    "arg1",
+						Type:    "string",
+						Value:   "some-value",
+						Indexed: true,
+					},
+				},
+				EventName: "SomeEvent",
+				OutputData: map[string]interface{}{
+					"output": "data",
+				},
+			}
+
+			txLog := &storage.TransactionLog{
+				TransactionHash:  insertedTransactions[0].TransactionHash,
+				TransactionIndex: insertedTransactions[0].TransactionIndex,
+				BlockNumber:      insertedTransactions[0].BlockNumber,
+			}
 
+			_, err := sqliteStore.InsertTransactionLog(
+				txLog.TransactionHash,
+				txLog.TransactionIndex,
+				txLog.BlockNumber,
+				decodedLog,
+				decodedLog.OutputData,
+			)
+			assert.NotNil(t, err)
+			assert.Contains(t, err.Error(), "UNIQUE constraint failed")
+		})
 	})
 }

From 3884e654f7889c8435b2e2ecaae7526ab78c166c Mon Sep 17 00:00:00 2001
From: Sean McGary <sean@seanmcgary.com>
Date: Fri, 6 Sep 2024 16:02:35 -0500
Subject: [PATCH 6/6] Convert EigenState models to use sqlite

---
 .../avsOperators/avsOperators_test.go         | 12 +++++--
 internal/eigenState/eigenstate_test.go        | 12 +++++--
 .../operatorShares/operatorShares_test.go     | 12 +++++--
 .../stakerDelegations_test.go                 | 12 +++++--
 .../202409061250_eigenlayerStateTables/up.go  | 34 +++++++++++++++++++
 .../202409061720_operatorShareChanges/up.go   | 31 +++++++++++++++++
 .../202409062151_stakerDelegations/up.go      | 30 ++++++++++++++++
 .../sqlite/migrations/migrator.go             |  6 ++++
 internal/sqlite/sqlite.go                     |  5 ++-
 internal/storage/sqlite/sqlite_test.go        |  4 +--
 10 files changed, 146 insertions(+), 12 deletions(-)
 create mode 100644 internal/sqlite/migrations/202409061250_eigenlayerStateTables/up.go
 create mode 100644 internal/sqlite/migrations/202409061720_operatorShareChanges/up.go
 create mode 100644 internal/sqlite/migrations/202409062151_stakerDelegations/up.go
 rename internal/{storage => }/sqlite/migrations/migrator.go (82%)

diff --git a/internal/eigenState/avsOperators/avsOperators_test.go b/internal/eigenState/avsOperators/avsOperators_test.go
index f32d985b..f2357d19 100644
--- a/internal/eigenState/avsOperators/avsOperators_test.go
+++ b/internal/eigenState/avsOperators/avsOperators_test.go
@@ -5,6 +5,7 @@ import (
 	"github.com/Layr-Labs/sidecar/internal/config"
 	"github.com/Layr-Labs/sidecar/internal/eigenState/stateManager"
 	"github.com/Layr-Labs/sidecar/internal/logger"
+	"github.com/Layr-Labs/sidecar/internal/sqlite/migrations"
 	"github.com/Layr-Labs/sidecar/internal/storage"
 	"github.com/Layr-Labs/sidecar/internal/tests"
 	"github.com/stretchr/testify/assert"
@@ -23,9 +24,16 @@ func setup() (
 	cfg := tests.GetConfig()
 	l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug})
 
-	_, grm, err := tests.GetDatabaseConnection(cfg)
+	db, err := tests.GetSqliteDatabaseConnection()
+	if err != nil {
+		panic(err)
+	}
+	sqliteMigrator := migrations.NewSqliteMigrator(db, l)
+	if err := sqliteMigrator.MigrateAll(); err != nil {
+		l.Sugar().Fatalw("Failed to migrate", "error", err)
+	}
 
-	return cfg, grm, l, err
+	return cfg, db, l, err
 }
 
 func teardown(model *AvsOperatorsModel) {
diff --git a/internal/eigenState/eigenstate_test.go b/internal/eigenState/eigenstate_test.go
index ec098f0a..083dc74b 100644
--- a/internal/eigenState/eigenstate_test.go
+++ b/internal/eigenState/eigenstate_test.go
@@ -7,6 +7,7 @@ import (
 	"github.com/Layr-Labs/sidecar/internal/eigenState/operatorShares"
 	"github.com/Layr-Labs/sidecar/internal/eigenState/stateManager"
 	"github.com/Layr-Labs/sidecar/internal/logger"
+	"github.com/Layr-Labs/sidecar/internal/sqlite/migrations"
 	"github.com/Layr-Labs/sidecar/internal/tests"
 	"github.com/stretchr/testify/assert"
 	"go.uber.org/zap"
@@ -23,9 +24,16 @@ func setup() (
 	cfg := tests.GetConfig()
 	l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug})
 
-	_, grm, err := tests.GetDatabaseConnection(cfg)
+	db, err := tests.GetSqliteDatabaseConnection()
+	if err != nil {
+		panic(err)
+	}
+	sqliteMigrator := migrations.NewSqliteMigrator(db, l)
+	if err := sqliteMigrator.MigrateAll(); err != nil {
+		l.Sugar().Fatalw("Failed to migrate", "error", err)
+	}
 
-	return cfg, grm, l, err
+	return cfg, db, l, err
 }
 
 func teardown(grm *gorm.DB) {
diff --git a/internal/eigenState/operatorShares/operatorShares_test.go b/internal/eigenState/operatorShares/operatorShares_test.go
index 2bfbb796..d71da5bc 100644
--- a/internal/eigenState/operatorShares/operatorShares_test.go
+++ b/internal/eigenState/operatorShares/operatorShares_test.go
@@ -6,6 +6,7 @@ import (
 	"github.com/Layr-Labs/sidecar/internal/config"
 	"github.com/Layr-Labs/sidecar/internal/eigenState/stateManager"
 	"github.com/Layr-Labs/sidecar/internal/logger"
+	"github.com/Layr-Labs/sidecar/internal/sqlite/migrations"
 	"github.com/Layr-Labs/sidecar/internal/storage"
 	"github.com/Layr-Labs/sidecar/internal/tests"
 	"github.com/stretchr/testify/assert"
@@ -25,9 +26,16 @@ func setup() (
 	cfg := tests.GetConfig()
 	l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug})
 
-	_, grm, err := tests.GetDatabaseConnection(cfg)
+	db, err := tests.GetSqliteDatabaseConnection()
+	if err != nil {
+		panic(err)
+	}
+	sqliteMigrator := migrations.NewSqliteMigrator(db, l)
+	if err := sqliteMigrator.MigrateAll(); err != nil {
+		l.Sugar().Fatalw("Failed to migrate", "error", err)
+	}
 
-	return cfg, grm, l, err
+	return cfg, db, l, err
 }
 
 func teardown(model *OperatorSharesModel) {
diff --git a/internal/eigenState/stakerDelegations/stakerDelegations_test.go b/internal/eigenState/stakerDelegations/stakerDelegations_test.go
index 088a755b..9e3b7d94 100644
--- a/internal/eigenState/stakerDelegations/stakerDelegations_test.go
+++ b/internal/eigenState/stakerDelegations/stakerDelegations_test.go
@@ -5,6 +5,7 @@ import (
 	"github.com/Layr-Labs/sidecar/internal/config"
 	"github.com/Layr-Labs/sidecar/internal/eigenState/stateManager"
 	"github.com/Layr-Labs/sidecar/internal/logger"
+	"github.com/Layr-Labs/sidecar/internal/sqlite/migrations"
 	"github.com/Layr-Labs/sidecar/internal/storage"
 	"github.com/Layr-Labs/sidecar/internal/tests"
 	"github.com/stretchr/testify/assert"
@@ -23,9 +24,16 @@ func setup() (
 	cfg := tests.GetConfig()
 	l, _ := logger.NewLogger(&logger.LoggerConfig{Debug: cfg.Debug})
 
-	_, grm, err := tests.GetDatabaseConnection(cfg)
+	db, err := tests.GetSqliteDatabaseConnection()
+	if err != nil {
+		panic(err)
+	}
+	sqliteMigrator := migrations.NewSqliteMigrator(db, l)
+	if err := sqliteMigrator.MigrateAll(); err != nil {
+		l.Sugar().Fatalw("Failed to migrate", "error", err)
+	}
 
-	return cfg, grm, l, err
+	return cfg, db, l, err
 }
 
 func teardown(model *StakerDelegationsModel) {
diff --git a/internal/sqlite/migrations/202409061250_eigenlayerStateTables/up.go b/internal/sqlite/migrations/202409061250_eigenlayerStateTables/up.go
new file mode 100644
index 00000000..f3d4c8e7
--- /dev/null
+++ b/internal/sqlite/migrations/202409061250_eigenlayerStateTables/up.go
@@ -0,0 +1,34 @@
+package _202409061250_eigenlayerStateTables
+
+import (
+	"fmt"
+	"gorm.io/gorm"
+)
+
+type SqliteMigration struct {
+}
+
+func (m *SqliteMigration) Up(grm *gorm.DB) error {
+	queries := []string{
+		`create table if not exists registered_avs_operators (
+			operator TEXT NOT NULL,
+			avs TEXT NOT NULL,
+			block_number INTEGER NOT NULL,
+			created_at DATETIME default current_timestamp,
+			unique(operator, avs, block_number)
+		);
+		`,
+	}
+
+	for _, query := range queries {
+		if res := grm.Exec(query); res.Error != nil {
+			fmt.Printf("Failed to execute query: %s\n", query)
+			return res.Error
+		}
+	}
+	return nil
+}
+
+func (m *SqliteMigration) GetName() string {
+	return "202409061250_eigenlayerStateTables"
+}
diff --git a/internal/sqlite/migrations/202409061720_operatorShareChanges/up.go b/internal/sqlite/migrations/202409061720_operatorShareChanges/up.go
new file mode 100644
index 00000000..c95d18ed
--- /dev/null
+++ b/internal/sqlite/migrations/202409061720_operatorShareChanges/up.go
@@ -0,0 +1,31 @@
+package _202409061720_operatorShareChanges
+
+import (
+	"gorm.io/gorm"
+)
+
+type SqliteMigration struct {
+}
+
+func (m *SqliteMigration) Up(grm *gorm.DB) error {
+	queries := []string{
+		`create table if not exists operator_shares (
+			operator TEXT NOT NULL,
+			strategy TEXT NOT NULL,
+			shares NUMERIC NOT NULL,
+			block_number INTEGER NOT NULL,
+			created_at DATETIME default current_timestamp,
+			unique (operator, strategy, block_number)
+		)`,
+	}
+	for _, query := range queries {
+		if res := grm.Exec(query); res.Error != nil {
+			return res.Error
+		}
+	}
+	return nil
+}
+
+func (m *SqliteMigration) GetName() string {
+	return "202409061720_operatorShareChanges"
+}
diff --git a/internal/sqlite/migrations/202409062151_stakerDelegations/up.go b/internal/sqlite/migrations/202409062151_stakerDelegations/up.go
new file mode 100644
index 00000000..7fa3465d
--- /dev/null
+++ b/internal/sqlite/migrations/202409062151_stakerDelegations/up.go
@@ -0,0 +1,30 @@
+package _202409062151_stakerDelegations
+
+import (
+	"gorm.io/gorm"
+)
+
+type SqliteMigration struct {
+}
+
+func (m *SqliteMigration) Up(grm *gorm.DB) error {
+	queries := []string{
+		`create table if not exists delegated_stakers (
+			staker TEXT NOT NULL,
+			operator TEXT NOT NULL,
+			block_number INTEGER NOT NULL,
+			created_at DATETIME default current_timestamp,
+			unique(staker, operator, block_number)
+		)`,
+	}
+	for _, query := range queries {
+		if res := grm.Exec(query); res.Error != nil {
+			return res.Error
+		}
+	}
+	return nil
+}
+
+func (m *SqliteMigration) GetName() string {
+	return "202409062151_stakerDelegations"
+}
diff --git a/internal/storage/sqlite/migrations/migrator.go b/internal/sqlite/migrations/migrator.go
similarity index 82%
rename from internal/storage/sqlite/migrations/migrator.go
rename to internal/sqlite/migrations/migrator.go
index 366e9f33..8baf1cd4 100644
--- a/internal/storage/sqlite/migrations/migrator.go
+++ b/internal/sqlite/migrations/migrator.go
@@ -4,6 +4,9 @@ import (
 	"database/sql"
 	"fmt"
 	_202409061249_bootstrapDb "github.com/Layr-Labs/sidecar/internal/sqlite/migrations/202409061249_bootstrapDb"
+	_202409061250_eigenlayerStateTables "github.com/Layr-Labs/sidecar/internal/sqlite/migrations/202409061250_eigenlayerStateTables"
+	_202409061720_operatorShareChanges "github.com/Layr-Labs/sidecar/internal/sqlite/migrations/202409061720_operatorShareChanges"
+	_202409062151_stakerDelegations "github.com/Layr-Labs/sidecar/internal/sqlite/migrations/202409062151_stakerDelegations"
 	"go.uber.org/zap"
 	"gorm.io/gorm"
 	"time"
@@ -35,6 +38,9 @@ func (m *SqliteMigrator) MigrateAll() error {
 
 	migrations := []ISqliteMigration{
 		&_202409061249_bootstrapDb.SqliteMigration{},
+		&_202409061250_eigenlayerStateTables.SqliteMigration{},
+		&_202409061720_operatorShareChanges.SqliteMigration{},
+		&_202409062151_stakerDelegations.SqliteMigration{},
 	}
 
 	for _, migration := range migrations {
diff --git a/internal/sqlite/sqlite.go b/internal/sqlite/sqlite.go
index 8ad06264..fc7041e5 100644
--- a/internal/sqlite/sqlite.go
+++ b/internal/sqlite/sqlite.go
@@ -3,6 +3,7 @@ package sqlite
 import (
 	"gorm.io/driver/sqlite"
 	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
 )
 
 func NewSqlite(path string) gorm.Dialector {
@@ -11,7 +12,9 @@ func NewSqlite(path string) gorm.Dialector {
 }
 
 func NewGormSqliteFromSqlite(sqlite gorm.Dialector) (*gorm.DB, error) {
-	db, err := gorm.Open(sqlite, &gorm.Config{})
+	db, err := gorm.Open(sqlite, &gorm.Config{
+		Logger: logger.Default.LogMode(logger.Silent),
+	})
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/storage/sqlite/sqlite_test.go b/internal/storage/sqlite/sqlite_test.go
index fd0af96a..6c88b8c5 100644
--- a/internal/storage/sqlite/sqlite_test.go
+++ b/internal/storage/sqlite/sqlite_test.go
@@ -2,12 +2,11 @@ package sqlite
 
 import (
 	"encoding/json"
-	"fmt"
 	"github.com/Layr-Labs/sidecar/internal/config"
 	"github.com/Layr-Labs/sidecar/internal/logger"
 	"github.com/Layr-Labs/sidecar/internal/parser"
+	"github.com/Layr-Labs/sidecar/internal/sqlite/migrations"
 	"github.com/Layr-Labs/sidecar/internal/storage"
-	"github.com/Layr-Labs/sidecar/internal/storage/sqlite/migrations"
 	"github.com/Layr-Labs/sidecar/internal/tests"
 	"github.com/stretchr/testify/assert"
 	"go.uber.org/zap"
@@ -82,7 +81,6 @@ func Test_SqliteBlockstore(t *testing.T) {
 			_, err := sqliteStore.InsertBlockAtHeight(block.Number, block.Hash, uint64(block.BlockTime.Unix()))
 			assert.NotNil(t, err)
 			assert.Contains(t, err.Error(), "UNIQUE constraint failed")
-			fmt.Printf("Error: %v\n", err)
 		})
 	})
 	t.Run("Transactions", func(t *testing.T) {