Commit fbbf3b20 authored by Hamdi Allam's avatar Hamdi Allam

schemas and database module

parent a2b038ec
package database
import (
"database/sql"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/jackc/pgtype"
"gorm.io/gorm"
)
/**
* Types
*/
type BlockHeader struct {
Hash common.Hash `gorm:"primaryKey"`
ParentHash common.Hash `gorm:"unique"`
Number pgtype.Numeric `gorm:"unique"`
Timestamp uint64
}
type L1BlockHeader struct {
*BlockHeader
}
type L2BlockHeader struct {
*BlockHeader
// Marked when the proposed output is finalized on L1.
// All bedrock blocks will have `LegacyStateBatchIndex == NULL`
L1BlockHash *common.Hash
LegacyStateBatchIndex sql.NullInt64
}
type LegacyStateBatch struct {
Index uint64 `gorm:"primaryKey"`
Root common.Hash `gorm:"unique"`
Size uint64
PrevTotal uint64
L1BlockHash common.Hash
}
type BlocksView interface {
LatestL1BlockHeight() (*big.Int, error)
LatestL2BlockHeight() (*big.Int, error)
}
type BlocksDB interface {
BlocksView
StoreL1BlockHeaders([]*L1BlockHeader) error
StoreLegacyStateBatch(*LegacyStateBatch) error
StoreL2BlockHeaders([]*L2BlockHeader) error
MarkFinalizedL1RootForL2Block(common.Hash, common.Hash) error
}
/**
* Implementation
*/
type blocksDB struct {
gorm *gorm.DB
}
func newBlocksDB(db *gorm.DB) BlocksDB {
return &blocksDB{gorm: db}
}
// L1
func (db *blocksDB) StoreL1BlockHeaders(headers []*L1BlockHeader) error {
result := db.gorm.Create(&headers)
return result.Error
}
func (db *blocksDB) StoreLegacyStateBatch(stateBatch *LegacyStateBatch) error {
// Event though transaction control flow is managed, could we benefit
// from a nested transaction here?
result := db.gorm.Create(stateBatch)
if result.Error != nil {
return result.Error
}
// Mark this index & l1 block hash for all applicable l2 blocks
l2Headers := make([]L2BlockHeader, stateBatch.Size)
// [start, end] range is inclusive. Since `PrevTotal` is the index of the prior batch, no
// need to substract one when adding the size
startHeight := pgtype.Numeric{Int: big.NewInt(int64(stateBatch.PrevTotal + 1)), Status: pgtype.Present}
endHeight := pgtype.Numeric{Int: big.NewInt(int64(stateBatch.PrevTotal + stateBatch.Size)), Status: pgtype.Present}
result = db.gorm.Where("number BETWEEN ? AND ?", &startHeight, &endHeight).Find(&l2Headers)
if result.Error != nil {
return result.Error
}
for _, header := range l2Headers {
header.LegacyStateBatchIndex = sql.NullInt64{Int64: int64(stateBatch.Index), Valid: true}
header.L1BlockHash = &stateBatch.L1BlockHash
}
result = db.gorm.Save(&l2Headers)
return result.Error
}
func (db *blocksDB) LatestL1BlockHeight() (*big.Int, error) {
var latestHeader L1BlockHeader
result := db.gorm.Order("number desc").First(&latestHeader)
if result.Error != nil {
return nil, result.Error
}
return latestHeader.Number.Int, nil
}
// L2
func (db *blocksDB) StoreL2BlockHeaders(headers []*L2BlockHeader) error {
result := db.gorm.Create(&headers)
return result.Error
}
func (db *blocksDB) LatestL2BlockHeight() (*big.Int, error) {
var latestHeader L2BlockHeader
result := db.gorm.Order("number desc").First(&latestHeader)
if result.Error != nil {
return nil, result.Error
}
return latestHeader.Number.Int, nil
}
func (db *blocksDB) MarkFinalizedL1RootForL2Block(l2Root, l1Root common.Hash) error {
var l2Header L2BlockHeader
result := db.gorm.First(&l2Header, "hash = ?", l2Root)
if result.Error == nil {
l2Header.L1BlockHash = &l1Root
db.gorm.Save(&l2Header)
}
return result.Error
}
package database
import (
"database/sql"
"github.com/ethereum/go-ethereum/common"
"github.com/jackc/pgtype"
"gorm.io/gorm"
)
/**
* Types
*/
type Transaction struct {
fromAddress common.Address
toAddress common.Address
amount pgtype.Numeric
data []byte
}
type TokenPair struct {
l1TokenAddress common.Address
l2TokenAddress common.Address
}
type Deposit struct {
GUID string `gorm:"primaryKey"`
InitiatedL1EventGUID string
Tx Transaction `gorm:"embedded"`
TokenPair TokenPair `gorm:"embedded"`
}
type DepositWithTransactionHash struct {
Deposit *Deposit `gorm:"embedded"`
L1TransactionHash common.Hash
}
type Withdrawal struct {
GUID string `gorm:"primaryKey"`
InitiatedL2EventGUID string
WithdrawalHash common.Hash
ProvenL1EventGUID sql.NullString
FinalizedL1EventGUID sql.NullString
Tx Transaction `gorm:"embedded"`
TokenPair TokenPair `gorm:"embedded"`
}
type WithdrawalWithTransactionHashes struct {
Withdrawal *Withdrawal `gorm:"embedded"`
L2TransactionHash common.Hash
ProvenL1TransactionHash *common.Hash
FinalizedL1TransactionHash *common.Hash
}
type BridgeView interface {
DepositsByAddress(address common.Address) ([]*DepositWithTransactionHash, error)
WithdrawalsByAddress(address common.Address) ([]*WithdrawalWithTransactionHashes, error)
}
type BridgeDB interface {
BridgeView
StoreDeposits([]*Deposit) error
StoreWithdrawals([]*Withdrawal) error
MarkProvenWithdrawalEvent(string, string) error
MarkFinalizedWithdrawalEvent(string, string) error
}
/**
* Implementation
*/
type bridgeDB struct {
gorm *gorm.DB
}
func newBridgeDB(db *gorm.DB) BridgeDB {
return &bridgeDB{gorm: db}
}
// Deposits
func (db *bridgeDB) StoreDeposits(deposits []*Deposit) error {
result := db.gorm.Create(&deposits)
return result.Error
}
func (db *bridgeDB) DepositsByAddress(address common.Address) ([]*DepositWithTransactionHash, error) {
// validate this query
depositsQuery := db.gorm.Table("deposits").Where("from_address = ?", address).Select("deposits.*")
joinQuery := depositsQuery.Joins("left join l1_contract_events transaction_hash as l1_transaction_hash ON deposit.initiated_l1_event_guid = l1_contract_events.guid")
deposits := []DepositWithTransactionHash{}
result := joinQuery.Scan(&deposits)
if result.Error != nil {
return nil, result.Error
}
depositPtrs := make([]*DepositWithTransactionHash, len(deposits))
for i, deposit := range deposits {
depositPtrs[i] = &deposit
}
return depositPtrs, nil
}
// Withdrawals
func (db *bridgeDB) StoreWithdrawals(withdrawals []*Withdrawal) error {
result := db.gorm.Create(&withdrawals)
return result.Error
}
func (db *bridgeDB) MarkProvenWithdrawalEvent(guid, provenL1EventGuid string) error {
var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "guid = ?", guid)
if result.Error == nil {
withdrawal.ProvenL1EventGUID = sql.NullString{String: provenL1EventGuid, Valid: true}
db.gorm.Save(&withdrawal)
}
return result.Error
}
func (db *bridgeDB) MarkFinalizedWithdrawalEvent(guid, finalizedL1EventGuid string) error {
var withdrawal Withdrawal
result := db.gorm.First(&withdrawal, "guid = ?", guid)
if result.Error == nil {
withdrawal.FinalizedL1EventGUID = sql.NullString{String: finalizedL1EventGuid, Valid: true}
db.gorm.Save(&withdrawal)
}
return result.Error
}
func (db *bridgeDB) WithdrawalsByAddress(address common.Address) ([]*WithdrawalWithTransactionHashes, error) {
// Implement this query
return nil, nil
}
package database
import (
"github.com/ethereum/go-ethereum/common"
"gorm.io/gorm"
)
/**
* Types
*/
type ContractEvent struct {
GUID string `gorm:"primaryKey"`
BlockHash common.Hash
TransactionHash common.Hash
EventSignature []byte
LogIndex uint64
}
type L1ContractEvent struct {
*ContractEvent
}
type L2ContractEvent struct {
*ContractEvent
}
type ContractEventsView interface {
L1ContractEventByGUID(string) (*L1ContractEvent, error)
L2ContractEventByGUID(string) (*L2ContractEvent, error)
}
type ContractEventsDB interface {
ContractEventsView
StoreL1ContractEvents([]*L1ContractEvent) error
StoreL2ContractEvents([]*L2ContractEvent) error
}
/**
* Implementation
*/
type contractEventsDB struct {
gorm *gorm.DB
}
func newContractEventsDB(db *gorm.DB) ContractEventsDB {
return &contractEventsDB{gorm: db}
}
// L1
func (db *contractEventsDB) StoreL1ContractEvents(events []*L1ContractEvent) error {
result := db.gorm.Create(&events)
return result.Error
}
func (db *contractEventsDB) L1ContractEventByGUID(guid string) (*L1ContractEvent, error) {
var event L1ContractEvent
result := db.gorm.First(&event, "guid = ?", guid)
if result.Error != nil {
return nil, result.Error
}
return &event, nil
}
// L2
func (db *contractEventsDB) StoreL2ContractEvents(events []*L2ContractEvent) error {
result := db.gorm.Create(&events)
return result.Error
}
func (db *contractEventsDB) L2ContractEventByGUID(guid string) (*L2ContractEvent, error) {
var event L2ContractEvent
result := db.gorm.First(&event, "guid = ?", guid)
if result.Error != nil {
return nil, result.Error
}
return &event, nil
}
package database
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type DB struct {
gorm *gorm.DB
Blocks BlocksDB
ContractEvents ContractEventsDB
Bridge BridgeDB
}
func NewDB(dsn string) (*DB, error) {
gorm, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
// The indexer will explictly manage the transaction
// flow processing blocks
SkipDefaultTransaction: true,
})
if err != nil {
return nil, err
}
db := &DB{
gorm: gorm,
Blocks: newBlocksDB(gorm),
ContractEvents: newContractEventsDB(gorm),
Bridge: newBridgeDB(gorm),
}
return db, nil
}
...@@ -80,9 +80,16 @@ require ( ...@@ -80,9 +80,16 @@ require (
github.com/ipfs/go-datastore v0.6.0 // indirect github.com/ipfs/go-datastore v0.6.0 // indirect
github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v5 v5.3.1 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect github.com/jbenet/goprocess v0.1.4 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.15.15 // indirect github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/koron/go-ssdp v0.0.3 // indirect github.com/koron/go-ssdp v0.0.3 // indirect
...@@ -159,20 +166,22 @@ require ( ...@@ -159,20 +166,22 @@ require (
go.uber.org/fx v1.19.1 // indirect go.uber.org/fx v1.19.1 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.6.0 // indirect golang.org/x/crypto v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb // indirect golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb // indirect
golang.org/x/mod v0.8.0 // indirect golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.9.0 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.5.0 // indirect golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.6.0 // indirect golang.org/x/tools v0.6.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/postgres v1.5.2 // indirect
gorm.io/gorm v1.25.1 // indirect
lukechampine.com/blake3 v1.1.7 // indirect lukechampine.com/blake3 v1.1.7 // indirect
nhooyr.io/websocket v1.8.7 // indirect nhooyr.io/websocket v1.8.7 // indirect
) )
This diff is collapsed.
/**
* BLOCK DATA
*/
CREATE TABLE l1_block_headers (
hash VARCHAR NOT NULL PRIMARY KEY,
parent_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash),
number NUMERIC NOT NULL,
timestamp INTEGER NOT NULL
);
CREATE TABLE l2_block_headers (
-- Block Header
hash VARCHAR NOT NULL PRIMARY KEY,
parent_hash VARCHAR NOT NULL REFERENCES l2_blocks(hash),
number NUMERIC NOT NULL,
timestamp INTEGER NOT NULL,
-- Finalization Information
l1_block_hash VARCHAR NOT NULL REFERENCES l1_bocks(hash),
legacy_state_batch_index INTEGER,
);
CREATE TABLE legacy_state_batches (
index INTEGER NOT NULL PRIMARY KEY,
root VARCHAR NOT NULL,
size INTEGER NOT NULL,
prev_total INTEGER NOT NULL,
-- Finalization Information
l1_block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash)
);
/**
* EVENT DATA
*/
CREATE TABLE l1_contract_events (
guid VARCHAR PRIMARY KEY NOT NULL,
block_hash VARCHAR NOT NULL REFERENCES l1_blocks(hash),
transaction_hash VARCHAR NOT NULL,
event_signature VARCHAR NOT NULL,
log_index INTEGER NOT NULL,
);
CREATE TABLE l2_contract_events (
guid VARCHAR PRIMARY KEY NOT NULL,
block_hash VARCHAR NOT NULL REFERENCES l2_blocks(hash),
transaction_hash VARCHAR NOT NULL,
event_signature VARCHAR NOT NULL,
log_index INTEGER NOT NULL,
);
/**
* Bridging Schemas
*/
CREATE TABLE deposits (
guid VARCHAR PRIMARY KEY NOT NULL,
-- Event causing the deposit
initiated_l1_event_guid VARCHAR NOT NULL REFERENCES l1_contract_events(guid),
-- Deposit Information (do we need indexes on from/to?)
from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL,
l1_token_address VARCHAR NOT NULL,
l2_token_address VARCHAR NOT NULL,
amount NUMERIC NOT NULL,
data BYTEA NOT NULL,
);
CREATE TABLE withdrawals (
guid VARCHAR PRIMARY KEY NOT NULL,
-- Event causing this withdrawal
intiated_l2_event_guid VARCHAR NOT NULL REFERENCES l2_contract_events(guid),
-- Multistep (bedrock) process of a withdrawal
withdrawal_hash VARCHAR NOT NULL,
proven_l1_event_guid VARCHAR REFERENCES l1_contract_events(guid)
-- Finalization marker (legacy & bedrock)
finalized_l1_event_guid VARCHAR REFERENCES l1_contract_events(guid)
-- Withdrawal Information (do we need indexes on from/to?)
from_address VARCHAR NOT NULL,
to_address VARCHAR NOT NULL,
l1_token_address VARCHAR NOT NULL,
l2_token_address VARCHAR NOT NULL,
amount NUMERIC NOT NULL,
data BYTEA NOT NULL,
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment