bridge_transactions.go 8.33 KB
Newer Older
1 2 3 4 5
package database

import (
	"errors"
	"fmt"
Hamdi Allam's avatar
Hamdi Allam committed
6
	"math/big"
7 8 9 10 11 12 13 14 15 16 17 18

	"github.com/google/uuid"
	"gorm.io/gorm"

	"github.com/ethereum/go-ethereum/common"
)

/**
 * Types
 */

type Transaction struct {
Hamdi Allam's avatar
Hamdi Allam committed
19 20
	FromAddress common.Address `gorm:"serializer:bytes"`
	ToAddress   common.Address `gorm:"serializer:bytes"`
Hamdi Allam's avatar
Hamdi Allam committed
21 22
	Amount      *big.Int       `gorm:"serializer:u256"`
	Data        Bytes          `gorm:"serializer:bytes"`
23 24 25 26
	Timestamp   uint64
}

type L1TransactionDeposit struct {
Hamdi Allam's avatar
Hamdi Allam committed
27 28
	SourceHash           common.Hash `gorm:"serializer:bytes;primaryKey"`
	L2TransactionHash    common.Hash `gorm:"serializer:bytes"`
29 30 31
	InitiatedL1EventGUID uuid.UUID

	Tx       Transaction `gorm:"embedded"`
Hamdi Allam's avatar
Hamdi Allam committed
32
	GasLimit *big.Int    `gorm:"serializer:u256"`
33 34 35
}

type L2TransactionWithdrawal struct {
Hamdi Allam's avatar
Hamdi Allam committed
36
	WithdrawalHash       common.Hash `gorm:"serializer:bytes;primaryKey"`
Hamdi Allam's avatar
Hamdi Allam committed
37
	Nonce                *big.Int    `gorm:"serializer:u256"`
38 39 40 41
	InitiatedL2EventGUID uuid.UUID

	ProvenL1EventGUID    *uuid.UUID
	FinalizedL1EventGUID *uuid.UUID
42
	Succeeded            *bool
43 44

	Tx       Transaction `gorm:"embedded"`
Hamdi Allam's avatar
Hamdi Allam committed
45
	GasLimit *big.Int    `gorm:"serializer:u256"`
46 47 48 49
}

type BridgeTransactionsView interface {
	L1TransactionDeposit(common.Hash) (*L1TransactionDeposit, error)
50 51
	L1LatestBlockHeader() (*L1BlockHeader, error)

52
	L2TransactionWithdrawal(common.Hash) (*L2TransactionWithdrawal, error)
53
	L2LatestBlockHeader() (*L2BlockHeader, error)
54 55 56 57 58
}

type BridgeTransactionsDB interface {
	BridgeTransactionsView

59
	StoreL1TransactionDeposits([]L1TransactionDeposit) error
60

61
	StoreL2TransactionWithdrawals([]L2TransactionWithdrawal) error
62
	MarkL2TransactionWithdrawalProvenEvent(common.Hash, uuid.UUID) error
63
	MarkL2TransactionWithdrawalFinalizedEvent(common.Hash, uuid.UUID, bool) error
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
}

/**
 * Implementation
 */

type bridgeTransactionsDB struct {
	gorm *gorm.DB
}

func newBridgeTransactionsDB(db *gorm.DB) BridgeTransactionsDB {
	return &bridgeTransactionsDB{gorm: db}
}

/**
 * Transactions deposited from L1
 */

82
func (db *bridgeTransactionsDB) StoreL1TransactionDeposits(deposits []L1TransactionDeposit) error {
Hamdi Allam's avatar
Hamdi Allam committed
83
	result := db.gorm.CreateInBatches(&deposits, batchInsertSize)
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
	return result.Error
}

func (db *bridgeTransactionsDB) L1TransactionDeposit(sourceHash common.Hash) (*L1TransactionDeposit, error) {
	var deposit L1TransactionDeposit
	result := db.gorm.Where(&L1TransactionDeposit{SourceHash: sourceHash}).Take(&deposit)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, result.Error
	}

	return &deposit, nil
}

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
func (db *bridgeTransactionsDB) L1LatestBlockHeader() (*L1BlockHeader, error) {
	// Markers for an indexed bridge event
	// L1: Latest Transaction Deposit, Latest Proven/Finalized Withdrawal
	l1DepositQuery := db.gorm.Table("l1_transaction_deposits").Order("l1_transaction_deposits.timestamp DESC").Limit(1)
	l1DepositQuery = l1DepositQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l1_transaction_deposits.initiated_l1_event_guid")
	l1DepositQuery = l1DepositQuery.Select("l1_contract_events.*")

	l1ProvenQuery := db.gorm.Table("l2_transaction_withdrawals")
	l1ProvenQuery = l1ProvenQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_transaction_withdrawals.proven_l1_event_guid")
	l1ProvenQuery = l1ProvenQuery.Order("l1_contract_events.timestamp DESC").Select("l1_contract_events.*").Limit(1)

	l1FinalizedQuery := db.gorm.Table("l2_transaction_withdrawals")
	l1FinalizedQuery = l1FinalizedQuery.Joins("INNER JOIN l1_contract_events ON l1_contract_events.guid = l2_transaction_withdrawals.proven_l1_event_guid")
	l1FinalizedQuery = l1FinalizedQuery.Order("l1_contract_events.timestamp DESC").Select("l1_contract_events.*").Limit(1)

	l1Query := db.gorm.Table("((?) UNION (?) UNION (?)) AS latest_bridge_events", l1DepositQuery.Limit(1), l1ProvenQuery, l1FinalizedQuery)
	l1Query = l1Query.Joins("INNER JOIN l1_block_headers ON l1_block_headers.hash = latest_bridge_events.block_hash")
117
	l1Query = l1Query.Order("latest_bridge_events.timestamp DESC").Select("l1_block_headers.*")
118 119 120 121 122 123 124 125 126 127 128 129 130

	var l1Header L1BlockHeader
	result := l1Query.Take(&l1Header)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, result.Error
	}

	return &l1Header, nil
}

131 132 133 134
/**
 * Transactions withdrawn from L2
 */

135
func (db *bridgeTransactionsDB) StoreL2TransactionWithdrawals(withdrawals []L2TransactionWithdrawal) error {
Hamdi Allam's avatar
Hamdi Allam committed
136
	result := db.gorm.CreateInBatches(&withdrawals, batchInsertSize)
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
	return result.Error
}

func (db *bridgeTransactionsDB) L2TransactionWithdrawal(withdrawalHash common.Hash) (*L2TransactionWithdrawal, error) {
	var withdrawal L2TransactionWithdrawal
	result := db.gorm.Where(&L2TransactionWithdrawal{WithdrawalHash: withdrawalHash}).Take(&withdrawal)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, result.Error
	}

	return &withdrawal, nil
}

// MarkL2TransactionWithdrawalProvenEvent links a withdrawn transaction with associated Prove action on L1.
func (db *bridgeTransactionsDB) MarkL2TransactionWithdrawalProvenEvent(withdrawalHash common.Hash, provenL1EventGuid uuid.UUID) error {
	withdrawal, err := db.L2TransactionWithdrawal(withdrawalHash)
	if err != nil {
		return err
Hamdi Allam's avatar
Hamdi Allam committed
158 159
	}
	if withdrawal == nil {
160 161 162 163 164 165 166 167 168
		return fmt.Errorf("transaction withdrawal hash %s not found", withdrawalHash)
	}

	withdrawal.ProvenL1EventGUID = &provenL1EventGuid
	result := db.gorm.Save(&withdrawal)
	return result.Error
}

// MarkL2TransactionWithdrawalProvenEvent links a withdrawn transaction in its finalized state
169
func (db *bridgeTransactionsDB) MarkL2TransactionWithdrawalFinalizedEvent(withdrawalHash common.Hash, finalizedL1EventGuid uuid.UUID, succeeded bool) error {
170 171 172
	withdrawal, err := db.L2TransactionWithdrawal(withdrawalHash)
	if err != nil {
		return err
Hamdi Allam's avatar
Hamdi Allam committed
173 174
	}
	if withdrawal == nil {
175
		return fmt.Errorf("transaction withdrawal hash %s not found", withdrawalHash)
Hamdi Allam's avatar
Hamdi Allam committed
176 177
	}
	if withdrawal.ProvenL1EventGUID == nil {
178 179 180 181
		return fmt.Errorf("cannot mark unproven withdrawal hash %s as finalized", withdrawal.WithdrawalHash)
	}

	withdrawal.FinalizedL1EventGUID = &finalizedL1EventGuid
182
	withdrawal.Succeeded = &succeeded
183 184 185
	result := db.gorm.Save(&withdrawal)
	return result.Error
}
186 187

func (db *bridgeTransactionsDB) L2LatestBlockHeader() (*L2BlockHeader, error) {
188 189 190 191 192 193 194 195 196 197 198 199 200
	// L2: Latest Withdrawal, Latest L2 Header of indexed deposit epoch
	var latestWithdrawalHeader, latestL2DepositHeader *L2BlockHeader

	var withdrawHeader L2BlockHeader
	withdrawalQuery := db.gorm.Table("l2_transaction_withdrawals").Order("timestamp DESC").Limit(1)
	withdrawalQuery = withdrawalQuery.Joins("INNER JOIN l2_contract_events ON l2_contract_events.guid = l2_transaction_withdrawals.initiated_l2_event_guid")
	withdrawalQuery = withdrawalQuery.Joins("INNER JOIN l2_block_headers ON l2_block_headers.hash = l2_contract_events.block_hash")
	result := withdrawalQuery.Select("l2_block_headers.*").Take(&withdrawHeader)
	if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
		return nil, result.Error
	} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
		latestWithdrawalHeader = &withdrawHeader
	}
201

202 203 204 205 206 207
	// Check for any deposits that may have been included after the latest withdrawal. However, since the bridge
	// processor only inserts entries when the corresponding epoch has been indexed on both L1 and L2, we can
	// simply look for the latest L2 block with at <= time of the latest L1 deposit.
	var l1Deposit L1TransactionDeposit
	result = db.gorm.Table("l1_transaction_deposits").Order("timestamp DESC").Limit(1).Take(&l1Deposit)
	if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
208
		return nil, result.Error
209 210 211 212 213 214 215 216
	} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
		var l2DepositHeader L2BlockHeader
		result := db.gorm.Table("l2_block_headers").Order("timestamp DESC").Limit(1).Where("timestamp <= ?", l1Deposit.Tx.Timestamp).Take(&l2DepositHeader)
		if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil, result.Error
		} else if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
			latestL2DepositHeader = &l2DepositHeader
		}
217 218
	}

219 220 221 222 223 224 225 226 227 228 229 230
	// compare
	if latestWithdrawalHeader == nil {
		return latestL2DepositHeader, nil
	} else if latestL2DepositHeader == nil {
		return latestWithdrawalHeader, nil
	}

	if latestWithdrawalHeader.Timestamp >= latestL2DepositHeader.Timestamp {
		return latestWithdrawalHeader, nil
	} else {
		return latestL2DepositHeader, nil
	}
231
}