asd
This commit is contained in:
parent
fb150f3d16
commit
953ad55f19
|
@ -0,0 +1,170 @@
|
||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CachedClient struct {
|
||||||
|
*ClientWithFallback
|
||||||
|
db *DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCachedClient(ethClients []*EthClient, chainID uint64, db *sql.DB) *CachedClient {
|
||||||
|
return &CachedClient{
|
||||||
|
ClientWithFallback: NewClient(ethClients, chainID),
|
||||||
|
db: NewDB(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
|
header, err := c.db.GetBlockHeaderByHash(c.NetworkID(), hash)
|
||||||
|
if err == nil {
|
||||||
|
return header, nil
|
||||||
|
} else if err != sql.ErrNoRows {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to get header from cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err = c.ClientWithFallback.HeaderByHash(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.db.PutBlockHeader(c.NetworkID(), header)
|
||||||
|
if err != nil {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to put header into cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||||
|
block, err := c.db.GetBlockByHash(c.NetworkID(), hash)
|
||||||
|
if err == nil {
|
||||||
|
return block, nil
|
||||||
|
} else if err != sql.ErrNoRows {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to get block from cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err = c.ClientWithFallback.BlockByHash(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if block != nil {
|
||||||
|
err = c.db.PutBlock(c.NetworkID(), block)
|
||||||
|
if err != nil {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to put block into cache", "error", err)
|
||||||
|
}
|
||||||
|
err = c.db.PutTransactions(c.NetworkID(), block.Transactions())
|
||||||
|
if err != nil {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to put transactions into cache", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
||||||
|
block, err := c.db.GetBlockByNumber(c.NetworkID(), number)
|
||||||
|
if err == nil {
|
||||||
|
return block, nil
|
||||||
|
} else if err != sql.ErrNoRows {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to get block from cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err = c.ClientWithFallback.BlockByNumber(ctx, number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.db.PutBlock(c.NetworkID(), block)
|
||||||
|
if err != nil {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to put block into cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
||||||
|
header, err := c.db.GetBlockHeaderByNumber(c.NetworkID(), number)
|
||||||
|
if err == nil {
|
||||||
|
return header, nil
|
||||||
|
} else if err != sql.ErrNoRows {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to get header from cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err = c.ClientWithFallback.HeaderByNumber(ctx, number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.db.PutBlockHeader(c.NetworkID(), header)
|
||||||
|
if err != nil {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to put header into cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedClient) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) {
|
||||||
|
transaction, err := c.db.GetTransactionByHash(c.NetworkID(), hash)
|
||||||
|
if err == nil {
|
||||||
|
return transaction, false, nil
|
||||||
|
} else if err != sql.ErrNoRows {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to get transaction from cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction, pending, err := c.ClientWithFallback.TransactionByHash(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, pending, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pending {
|
||||||
|
err = c.db.PutTransactions(c.NetworkID(), types.Transactions{transaction})
|
||||||
|
if err != nil {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to put transaction into cache", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transaction, pending, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedClient) TransactionReceipt(ctx context.Context, hash common.Hash) (*types.Receipt, error) {
|
||||||
|
receipt, err := c.db.GetTransactionReceipt(c.NetworkID(), hash)
|
||||||
|
if err == nil {
|
||||||
|
return receipt, nil
|
||||||
|
} else if err != sql.ErrNoRows {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to get transaction receipt from cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
receipt, err = c.ClientWithFallback.TransactionReceipt(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.db.PutTransactionReceipt(c.NetworkID(), receipt)
|
||||||
|
if err != nil {
|
||||||
|
// Soft error, we can continue
|
||||||
|
log.Error("Failed to put transaction receipt into cache", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return receipt, nil
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/status-im/status-go/t/helpers"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/t/helpers"
|
||||||
|
"github.com/status-im/status-go/walletdatabase"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupDBTest(t *testing.T) (*DB, func()) {
|
||||||
|
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return NewDB(db), func() {
|
||||||
|
require.NoError(t, db.Close())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupCachedClient(t *testing.T) (*CachedClient, func()) {
|
||||||
|
db, closeDB := setupDBTest(t)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return NewDB(db), func() {
|
||||||
|
require.NoError(t, db.Close())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTransactionByHash(t *testing.T) {
|
||||||
|
db, cleanup := setupDBTest(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
txHash := common.HexToHash("0x123456789abcdef")
|
||||||
|
tx := types.NewTransaction(0, common.HexToAddress("0x1"), big.NewInt(1), 100000, big.NewInt(1), nil)
|
||||||
|
receipt := &types.Receipt{
|
||||||
|
TxHash: txHash,
|
||||||
|
GasUsed: 100000,
|
||||||
|
Status: types.ReceiptStatusSuccessful,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.PutTransaction(chainID, tx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
retrievedTx, err := db.GetTransactionByHash(chainID, txHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tx, retrievedTx)
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
crypto_rand "crypto/rand"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getRandomTransaction() *types.Transaction {
|
||||||
|
nonce := rand.Uint64()
|
||||||
|
gasLimit := rand.Uint64()
|
||||||
|
gasPrice := rand.Uint64()
|
||||||
|
to := common.Address{}
|
||||||
|
crypto_rand.Read(to[:])
|
||||||
|
value := rand.Uint64()
|
||||||
|
data := make([]byte, 32*rand.Intn(10))
|
||||||
|
crypto_rand.Read(data)
|
||||||
|
|
||||||
|
tx := types.NewTransaction(nonce, to, big.NewInt(int64(value)), gasLimit, big.NewInt(int64(gasPrice)), data)
|
||||||
|
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomBlockHeader() *types.Header {
|
||||||
|
header := &types.Header{
|
||||||
|
Number: big.NewInt(rand.Int63()),
|
||||||
|
Time: rand.Uint64(),
|
||||||
|
Difficulty: big.NewInt(rand.Int63()),
|
||||||
|
ParentHash: common.Hash{},
|
||||||
|
Nonce: types.BlockNonce{},
|
||||||
|
MixDigest: common.Hash{},
|
||||||
|
}
|
||||||
|
crypto_rand.Read(header.ParentHash[:])
|
||||||
|
crypto_rand.Read(header.Nonce[:])
|
||||||
|
crypto_rand.Read(header.MixDigest[:])
|
||||||
|
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomLog() *types.Log {
|
||||||
|
log := &types.Log{
|
||||||
|
Address: common.Address{},
|
||||||
|
Topics: []common.Hash{},
|
||||||
|
Data: []byte{},
|
||||||
|
BlockNumber: rand.Uint64(),
|
||||||
|
TxHash: common.Hash{},
|
||||||
|
TxIndex: uint(rand.Uint64()),
|
||||||
|
}
|
||||||
|
crypto_rand.Read(log.Address[:])
|
||||||
|
crypto_rand.Read(log.TxHash[:])
|
||||||
|
for i := 0; i < rand.Intn(10); i++ {
|
||||||
|
hash := common.Hash{}
|
||||||
|
crypto_rand.Read(hash[:])
|
||||||
|
log.Topics = append(log.Topics, hash)
|
||||||
|
}
|
||||||
|
crypto_rand.Read(log.Data)
|
||||||
|
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomReceipt() *types.Receipt {
|
||||||
|
receipt := &types.Receipt{
|
||||||
|
Status: rand.Uint64(),
|
||||||
|
CumulativeGasUsed: rand.Uint64(),
|
||||||
|
Bloom: types.Bloom{},
|
||||||
|
Logs: []*types.Log{},
|
||||||
|
}
|
||||||
|
crypto_rand.Read(receipt.Bloom[:])
|
||||||
|
for i := 0; i < rand.Intn(10); i++ {
|
||||||
|
receipt.Logs = append(receipt.Logs, getRandomLog())
|
||||||
|
}
|
||||||
|
|
||||||
|
return receipt
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRandomBlock() *types.Block {
|
||||||
|
header := getRandomBlockHeader()
|
||||||
|
|
||||||
|
txs := []*types.Transaction{}
|
||||||
|
for i := 0; i < rand.Intn(10); i++ {
|
||||||
|
txs = append(txs, getRandomTransaction())
|
||||||
|
}
|
||||||
|
|
||||||
|
receipts := []*types.Receipt{}
|
||||||
|
for i := 0; i < rand.Intn(10); i++ {
|
||||||
|
receipts = append(receipts, getRandomReceipt())
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewBlock(header, txs, nil, receipts, nil)
|
||||||
|
}
|
|
@ -161,25 +161,6 @@ var propagateErrors = []error{
|
||||||
bind.ErrNoCode,
|
bind.ErrNoCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSimpleClient(ethClient EthClient, chainID uint64) *ClientWithFallback {
|
|
||||||
cbConfig := circuitbreaker.Config{
|
|
||||||
Timeout: 20000,
|
|
||||||
MaxConcurrentRequests: 100,
|
|
||||||
SleepWindow: 300000,
|
|
||||||
ErrorPercentThreshold: 25,
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnected := &atomic.Bool{}
|
|
||||||
isConnected.Store(true)
|
|
||||||
return &ClientWithFallback{
|
|
||||||
ChainID: chainID,
|
|
||||||
ethClients: []*EthClient{ðClient},
|
|
||||||
isConnected: isConnected,
|
|
||||||
LastCheckedAt: time.Now().Unix(),
|
|
||||||
circuitbreaker: circuitbreaker.NewCircuitBreaker(cbConfig),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(ethClients []*EthClient, chainID uint64) *ClientWithFallback {
|
func NewClient(ethClients []*EthClient, chainID uint64) *ClientWithFallback {
|
||||||
cbConfig := circuitbreaker.Config{
|
cbConfig := circuitbreaker.Config{
|
||||||
Timeout: 20000,
|
Timeout: 20000,
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDB(db *sql.DB) *DB {
|
||||||
|
return &DB{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DB) GetBlockByNumber(chainID uint64, blockNumber *big.Int) (*types.Block, error) {
|
||||||
|
row := b.db.QueryRow("SELECT block_json FROM blockchain_data_blocks WHERE chain_id = ? AND block_number = ?", chainID, blockNumber)
|
||||||
|
var block types.Block
|
||||||
|
err := row.Scan(&block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DB) GetBlockByHash(chainID uint64, blockHash common.Hash) (*types.Block, error) {
|
||||||
|
row := b.db.QueryRow("SELECT block_json FROM blockchain_data_blocks WHERE chain_id = ? AND block_hash = ?", chainID, blockHash)
|
||||||
|
var block types.Block
|
||||||
|
err := row.Scan(&block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DB) GetBlockHeaderByNumber(chainID uint64, blockNumber *big.Int) (*types.Header, error) {
|
||||||
|
row := b.db.QueryRow("SELECT block_json FROM blockchain_data_blocks WHERE chain_id = ? AND block_number = ?", chainID, blockNumber)
|
||||||
|
var blockHeader types.Header
|
||||||
|
err := row.Scan(&blockHeader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &blockHeader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DB) GetBlockHeaderByHash(chainID uint64, blockHash common.Hash) (*types.Header, error) {
|
||||||
|
row := b.db.QueryRow("SELECT block_json FROM blockchain_data_blocks WHERE chain_id = ? AND block_hash = ?", chainID, blockHash)
|
||||||
|
var blockHeader types.Header
|
||||||
|
err := row.Scan(&blockHeader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &blockHeader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DB) PutBlock(chainID uint64, block *types.Block) error {
|
||||||
|
_, err := b.db.Exec("INSERT INTO blockchain_data_blocks (chain_id, block_number, block_hash, block_header_json, block_json) VALUES (?, ?, ?, ?, ?)", chainID, block.Number(), block.Hash(), block.Header(), block)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.PutTransactions(chainID, block.Transactions())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DB) PutBlockHeader(chainID uint64, blockHeader *types.Header) error {
|
||||||
|
_, err := b.db.Exec("INSERT INTO blockchain_data_blocks (chain_id, block_number, block_hash, block_header_json) VALUES (?, ?, ?, ?)", chainID, blockHeader.Number, blockHeader.Hash(), blockHeader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DB) GetTransactionsByBlockHash(chainID uint64, blockHash common.Hash) (types.Transactions, error) {
|
||||||
|
rows, err := t.db.Query("SELECT transaction_json FROM blockchain_data_transactions WHERE chain_id = ? AND block_hash = ?", chainID, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var transactions types.Transactions
|
||||||
|
for rows.Next() {
|
||||||
|
var transaction types.Transaction
|
||||||
|
err := rows.Scan(&transaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transactions = append(transactions, &transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DB) GetTransactionsByBlockNumber(chainID uint64, blockNumber *big.Int) (types.Transactions, error) {
|
||||||
|
rows, err := t.db.Query("SELECT transaction_json FROM blockchain_data_transactions WHERE chain_id = ? AND block_number = ?", chainID, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var transactions types.Transactions
|
||||||
|
for rows.Next() {
|
||||||
|
var transaction types.Transaction
|
||||||
|
err := rows.Scan(&transaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transactions = append(transactions, &transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DB) GetTransactionByHash(chainID uint64, transactionHash common.Hash) (*types.Transaction, error) {
|
||||||
|
row := t.db.QueryRow("SELECT transaction_json FROM blockchain_data_transactions WHERE chain_id = ? AND transaction_hash = ?", chainID, transactionHash)
|
||||||
|
var transaction types.Transaction
|
||||||
|
err := row.Scan(&transaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &transaction, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DB) PutTransactions(chainID uint64, transactions types.Transactions) error {
|
||||||
|
tx, err := t.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare("INSERT INTO blockchain_data_transactions (chain_id, transaction_hash, transaction_json) VALUES (?, ?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
for _, transaction := range transactions {
|
||||||
|
_, err = stmt.Exec(chainID, transaction.Hash(), transaction)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DB) GetTransactionReceipt(chainID uint64, transactionHash common.Hash) (*types.Receipt, error) {
|
||||||
|
row := t.db.QueryRow("SELECT receipt_json FROM blockchain_data_transactions_receipts WHERE chain_id = ? AND transaction_hash = ?", chainID, transactionHash)
|
||||||
|
var receipt types.Receipt
|
||||||
|
err := row.Scan(&receipt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &receipt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DB) PutTransactionReceipt(chainID uint64, receipt *types.Receipt) error {
|
||||||
|
tx, err := t.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare("INSERT INTO blockchain_data_transactions_receipts (chain_id, transaction_hash, receipt_json) VALUES (?, ?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
_, err = stmt.Exec(chainID, receipt.TxHash, receipt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/status-im/status-go/t/helpers"
|
||||||
|
"github.com/status-im/status-go/walletdatabase"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupDBTest(t *testing.T) (*DB, func()) {
|
||||||
|
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return NewDB(db), func() {
|
||||||
|
require.NoError(t, db.Close())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlockByNumber(t *testing.T) {
|
||||||
|
db, cleanup := setupDBTest(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
blockNumber := big.NewInt(123)
|
||||||
|
block := types.NewBlock(&types.Header{Number: blockNumber}, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
err := db.PutBlock(chainID, block)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
retrievedBlock, err := db.GetBlockByNumber(chainID, blockNumber)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, retrievedBlock)
|
||||||
|
assert.Equal(t, block.Hash(), retrievedBlock.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlockByHash(t *testing.T) {
|
||||||
|
db, cleanup := setupDBTest(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
blockNumber := big.NewInt(123)
|
||||||
|
block := types.NewBlock(&types.Header{Number: blockNumber}, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
err := db.PutBlock(chainID, block)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
retrievedBlock, err := db.GetBlockByHash(chainID, block.Hash())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, retrievedBlock)
|
||||||
|
assert.Equal(t, block.Number(), retrievedBlock.Number())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlockHeaderByNumber(t *testing.T) {
|
||||||
|
db, cleanup := setupDBTest(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
blockNumber := big.NewInt(123)
|
||||||
|
header := &types.Header{Number: blockNumber}
|
||||||
|
block := types.NewBlock(header, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
err := db.PutBlock(chainID, block)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
retrievedHeader, err := db.GetBlockHeaderByNumber(chainID, blockNumber)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, retrievedHeader)
|
||||||
|
assert.Equal(t, header.Hash(), retrievedHeader.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlockHeaderByHash(t *testing.T) {
|
||||||
|
db, cleanup := setupDBTest(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
blockNumber := big.NewInt(123)
|
||||||
|
header := &types.Header{Number: blockNumber}
|
||||||
|
block := types.NewBlock(header, nil, nil, nil, nil)
|
||||||
|
|
||||||
|
err := db.PutBlock(chainID, block)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
retrievedHeader, err := db.GetBlockHeaderByHash(chainID, block.Hash())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, retrievedHeader)
|
||||||
|
assert.Equal(t, header.Number, retrievedHeader.Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutAndGetTransactions(t *testing.T) {
|
||||||
|
db, cleanup := setupDBTest(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
blockNumber := big.NewInt(123)
|
||||||
|
tx1 := types.NewTransaction(0, common.Address{}, big.NewInt(100), 21000, big.NewInt(1), nil)
|
||||||
|
tx2 := types.NewTransaction(1, common.Address{}, big.NewInt(200), 21000, big.NewInt(1), nil)
|
||||||
|
txs := types.Transactions{tx1, tx2}
|
||||||
|
|
||||||
|
block := types.NewBlock(&types.Header{Number: blockNumber}, txs, nil, nil, nil)
|
||||||
|
|
||||||
|
err := db.PutBlock(chainID, block)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Test GetTransactionsByBlockHash
|
||||||
|
retrievedTxs, err := db.GetTransactionsByBlockHash(chainID, block.Hash())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, retrievedTxs, 2)
|
||||||
|
assert.Equal(t, tx1.Hash(), retrievedTxs[0].Hash())
|
||||||
|
assert.Equal(t, tx2.Hash(), retrievedTxs[1].Hash())
|
||||||
|
|
||||||
|
// Test GetTransactionsByBlockNumber
|
||||||
|
retrievedTxs, err = db.GetTransactionsByBlockNumber(chainID, blockNumber)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, retrievedTxs, 2)
|
||||||
|
assert.Equal(t, tx1.Hash(), retrievedTxs[0].Hash())
|
||||||
|
assert.Equal(t, tx2.Hash(), retrievedTxs[1].Hash())
|
||||||
|
|
||||||
|
// Test GetTransactionByHash
|
||||||
|
retrievedTx, err := db.GetTransactionByHash(chainID, tx1.Hash())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, retrievedTx)
|
||||||
|
assert.Equal(t, tx1.Hash(), retrievedTx.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutAndGetTransactionReceipt(t *testing.T) {
|
||||||
|
db, cleanup := setupDBTest(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
chainID := uint64(1)
|
||||||
|
tx := types.NewTransaction(0, common.Address{}, big.NewInt(100), 21000, big.NewInt(1), nil)
|
||||||
|
receipt := &types.Receipt{
|
||||||
|
TxHash: tx.Hash(),
|
||||||
|
GasUsed: 21000,
|
||||||
|
Status: types.ReceiptStatusSuccessful,
|
||||||
|
BlockNumber: big.NewInt(123),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.PutTransactionReceipt(chainID, receipt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
retrievedReceipt, err := db.GetTransactionReceipt(chainID, tx.Hash())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, retrievedReceipt)
|
||||||
|
assert.Equal(t, receipt.TxHash, retrievedReceipt.TxHash)
|
||||||
|
assert.Equal(t, receipt.GasUsed, retrievedReceipt.GasUsed)
|
||||||
|
assert.Equal(t, receipt.Status, retrievedReceipt.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add more test functions as needed...
|
|
@ -104,6 +104,8 @@ type Client struct {
|
||||||
|
|
||||||
walletNotifier func(chainID uint64, message string)
|
walletNotifier func(chainID uint64, message string)
|
||||||
providerConfigs []params.ProviderConfig
|
providerConfigs []params.ProviderConfig
|
||||||
|
|
||||||
|
db *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is initialized in a build-tag-dependent module
|
// Is initialized in a build-tag-dependent module
|
||||||
|
@ -136,6 +138,7 @@ func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.U
|
||||||
limiterPerProvider: make(map[string]*chain.RPCRpsLimiter),
|
limiterPerProvider: make(map[string]*chain.RPCRpsLimiter),
|
||||||
log: log,
|
log: log,
|
||||||
providerConfigs: providerConfigs,
|
providerConfigs: providerConfigs,
|
||||||
|
db: db,
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts []gethrpc.ClientOption
|
var opts []gethrpc.ClientOption
|
||||||
|
@ -165,7 +168,10 @@ func NewClient(client *gethrpc.Client, upstreamChainID uint64, upstream params.U
|
||||||
// Include the chain-id in the rpc client
|
// Include the chain-id in the rpc client
|
||||||
rpcName := fmt.Sprintf("%s-chain-id-%d", hostPortUpstream, upstreamChainID)
|
rpcName := fmt.Sprintf("%s-chain-id-%d", hostPortUpstream, upstreamChainID)
|
||||||
|
|
||||||
c.upstream = chain.NewSimpleClient(*chain.NewEthClient(ethclient.NewClient(upstreamClient), limiter, upstreamClient, rpcName), upstreamChainID)
|
ethClients := []*chain.EthClient{
|
||||||
|
chain.NewEthClient(ethclient.NewClient(upstreamClient), limiter, upstreamClient, rpcName),
|
||||||
|
}
|
||||||
|
c.upstream = chain.NewCachedClient(ethClients, upstreamChainID, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.router = newRouter(c.upstreamEnabled)
|
c.router = newRouter(c.upstreamEnabled)
|
||||||
|
@ -233,7 +239,7 @@ func (c *Client) getClientUsingCache(chainID uint64) (chain.ClientInterface, err
|
||||||
return nil, fmt.Errorf("could not find any RPC URL for chain: %d", chainID)
|
return nil, fmt.Errorf("could not find any RPC URL for chain: %d", chainID)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := chain.NewClient(ethClients, chainID)
|
client := chain.NewCachedClient(ethClients, chainID, c.db)
|
||||||
client.WalletNotifier = c.walletNotifier
|
client.WalletNotifier = c.walletNotifier
|
||||||
c.rpcClients[chainID] = client
|
c.rpcClients[chainID] = client
|
||||||
return client, nil
|
return client, nil
|
||||||
|
@ -371,7 +377,12 @@ func (c *Client) UpdateUpstreamURL(url string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostPortUpstream = "upstream"
|
hostPortUpstream = "upstream"
|
||||||
}
|
}
|
||||||
c.upstream = chain.NewSimpleClient(*chain.NewEthClient(ethclient.NewClient(rpcClient), rpsLimiter, rpcClient, hostPortUpstream), c.UpstreamChainID)
|
|
||||||
|
ethClients := []*chain.EthClient{
|
||||||
|
chain.NewEthClient(chain.NewEthClient(ethclient.NewClient(rpcClient), rpsLimiter, rpcClient, hostPortUpstream)),
|
||||||
|
}
|
||||||
|
c.upstream = chain.NewCachedClient(ethClients, c.UpstreamChainID, c.db)
|
||||||
|
|
||||||
c.upstreamURL = url
|
c.upstreamURL = url
|
||||||
c.Unlock()
|
c.Unlock()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
-- store raw block headers
|
||||||
|
CREATE TABLE IF NOT EXISTS blockchain_data_blocks (
|
||||||
|
chain_id UNSIGNED BIGINT NOT NULL,
|
||||||
|
block_number BLOB NOT NULL,
|
||||||
|
block_hash BLOB NOT NULL,
|
||||||
|
block_header_json JSON NOT NULL,
|
||||||
|
block_json JSON,
|
||||||
|
CONSTRAINT unique_block_header_per_chain_per_block_number UNIQUE (chain_id,block_number) ON CONFLICT REPLACE,
|
||||||
|
CONSTRAINT unique_block_header_per_chain_per_block_hash UNIQUE (chain_id,block_hash) ON CONFLICT REPLACE
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_blockchain_data_block_headers_chain_id_block_number ON blockchain_data_block_headers (chain_id, block_number);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_blockchain_data_block_headers_chain_id_block_hash ON blockchain_data_block_headers (chain_id, block_hash);
|
||||||
|
|
||||||
|
-- store raw transactions
|
||||||
|
CREATE TABLE IF NOT EXISTS blockchain_data_transactions (
|
||||||
|
chain_id UNSIGNED BIGINT NOT NULL,
|
||||||
|
block_hash BLOB NOT NULL,
|
||||||
|
transaction_hash BLOB NOT NULL,
|
||||||
|
transaction_json JSON NOT NULL,
|
||||||
|
receipt_json JSON,
|
||||||
|
CONSTRAINT unique_transaction_per_chain_per_transaction_hash UNIQUE (chain_id, transaction_hash) ON CONFLICT REPLACE,
|
||||||
|
FOREIGN KEY(chain_id, block_hash) REFERENCES blockchain_data_block_headers(chain_id, block_hash)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_blockchain_data_transactions_chain_id_transaction_hash ON blockchain_data_transactions (chain_id, transaction_hash);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_blockchain_data_transactions_chain_id_block_hash ON blockchain_data_transactions (chain_id, block_hash);
|
Loading…
Reference in New Issue