Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bchain/coins/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/trezor/blockbook/bchain/coins/flo"
"github.com/trezor/blockbook/bchain/coins/fujicoin"
"github.com/trezor/blockbook/bchain/coins/gamecredits"
"github.com/trezor/blockbook/bchain/coins/gnosis"
"github.com/trezor/blockbook/bchain/coins/grs"
"github.com/trezor/blockbook/bchain/coins/koto"
"github.com/trezor/blockbook/bchain/coins/liquid"
Expand Down Expand Up @@ -152,6 +153,8 @@ func init() {
BlockChainFactories["Arbitrum Nova Archive"] = arbitrum.NewArbitrumRPC
BlockChainFactories["Base"] = base.NewBaseRPC
BlockChainFactories["Base Archive"] = base.NewBaseRPC
BlockChainFactories["Gnosis"] = gnosis.NewGnosisRPC
BlockChainFactories["Gnosis Archive"] = gnosis.NewGnosisRPC
}

// NewBlockChain creates bchain.BlockChain and bchain.Mempool for the coin passed by the parameter coin
Expand Down
10 changes: 7 additions & 3 deletions bchain/coins/eth/ethrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type EthereumRPC struct {
Parser *EthereumParser
PushHandler func(bchain.NotificationType)
OpenRPC func(string) (bchain.EVMRPCClient, bchain.EVMClient, error)
GetInternalDataForBlock func(blockHash string, blockHeight uint32, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, []bchain.ContractInfo, error)
Mempool *bchain.MempoolEthereumType
mempoolInitialized bool
bestHeaderLock sync.Mutex
Expand Down Expand Up @@ -106,6 +107,8 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
ChainConfig: &c,
}

// use debug_trace for internal data by default
s.GetInternalDataForBlock = s.getInternalDataForBlock
ProcessInternalTransactions = c.ProcessInternalTransactions

// always create parser
Expand Down Expand Up @@ -661,7 +664,8 @@ type rpcTraceResult struct {
Result rpcCallTrace `json:"result"`
}

func (b *EthereumRPC) getCreationContractInfo(contract string, height uint32) *bchain.ContractInfo {
// GetCreationContractInfo retrieves the info for a contract address and sets the creation block height
func (b *EthereumRPC) GetCreationContractInfo(contract string, height uint32) *bchain.ContractInfo {
// do not fetch fetchContractInfo in sync, it slows it down
// the contract will be fetched only when asked by a client
// ci, err := b.fetchContractInfo(contract)
Expand All @@ -688,7 +692,7 @@ func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInt
From: call.From,
To: call.To, // new contract address
})
contracts = append(contracts, *b.getCreationContractInfo(call.To, blockHeight))
contracts = append(contracts, *b.GetCreationContractInfo(call.To, blockHeight))
} else if call.Type == "SELFDESTRUCT" {
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Type: bchain.SELFDESTRUCT,
Expand Down Expand Up @@ -759,7 +763,7 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, blockHeight uint
if r.Type == "CREATE" || r.Type == "CREATE2" {
d.Type = bchain.CREATE
d.Contract = r.To
contracts = append(contracts, *b.getCreationContractInfo(d.Contract, blockHeight))
contracts = append(contracts, *b.GetCreationContractInfo(d.Contract, blockHeight))
} else if r.Type == "SELFDESTRUCT" {
d.Type = bchain.SELFDESTRUCT
}
Expand Down
140 changes: 140 additions & 0 deletions bchain/coins/gnosis/evm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package gnosis

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/trezor/blockbook/bchain"
)

// GnosisClient wraps a client to implement the EVMClient interface
type GnosisClient struct {
*ethclient.Client
*GnosisRPCClient
}

// HeaderByNumber returns a block header that implements the EVMHeader interface
func (c *GnosisClient) HeaderByNumber(ctx context.Context, number *big.Int) (bchain.EVMHeader, error) {
var head *Header
err := c.GnosisRPCClient.CallContext(ctx, &head, "eth_getBlockByNumber", bchain.ToBlockNumArg(number), false)
if err == nil && head == nil {
err = ethereum.NotFound
}
return &GnosisHeader{Header: head}, err
}

// EstimateGas returns the current estimated gas cost for executing a transaction
func (c *GnosisClient) EstimateGas(ctx context.Context, msg interface{}) (uint64, error) {
return c.Client.EstimateGas(ctx, msg.(ethereum.CallMsg))
}

// BalanceAt returns the balance for the given account at a specific block, or latest known block if no block number is provided
func (c *GnosisClient) BalanceAt(ctx context.Context, addrDesc bchain.AddressDescriptor, blockNumber *big.Int) (*big.Int, error) {
return c.Client.BalanceAt(ctx, common.BytesToAddress(addrDesc), blockNumber)
}

// NonceAt returns the nonce for the given account at a specific block, or latest known block if no block number is provided
func (c *GnosisClient) NonceAt(ctx context.Context, addrDesc bchain.AddressDescriptor, blockNumber *big.Int) (uint64, error) {
return c.Client.NonceAt(ctx, common.BytesToAddress(addrDesc), blockNumber)
}

// GnosisRPCClient wraps an rpc client to implement the EVMRPCClient interface
type GnosisRPCClient struct {
*rpc.Client
}

// EthSubscribe subscribes to events and returns a client subscription that implements the EVMClientSubscription interface
func (c *GnosisRPCClient) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (bchain.EVMClientSubscription, error) {
sub, err := c.Client.EthSubscribe(ctx, channel, args...)
if err != nil {
return nil, err
}

return &GnosisClientSubscription{ClientSubscription: sub}, nil
}

// GnosisHeader wraps a block header to implement the EVMHeader interface
type GnosisHeader struct {
*Header
}

// Hash returns the block hash as a hex string
func (h *GnosisHeader) Hash() string {
return h.Header.Hash().Hex()
}

// Number returns the block number
func (h *GnosisHeader) Number() *big.Int {
return h.Header.Number
}

// Difficulty returns the block difficulty
func (h *GnosisHeader) Difficulty() *big.Int {
return h.Header.Difficulty
}

// GnosisHash wraps a transaction hash to implement the EVMHash interface
type GnosisHash struct {
common.Hash
}

// GnosisClientSubscription wraps a client subcription to implement the EVMClientSubscription interface
type GnosisClientSubscription struct {
*rpc.ClientSubscription
}

// GnosisNewBlock wraps a block header channel to implement the EVMNewBlockSubscriber interface
type GnosisNewBlock struct {
channel chan *Header
}

// NewGnosisNewBlock returns an initialized GnosisNewBlock struct
func NewGnosisNewBlock() *GnosisNewBlock {
return &GnosisNewBlock{channel: make(chan *Header)}
}

// Channel returns the underlying channel as an empty interface
func (s *GnosisNewBlock) Channel() interface{} {
return s.channel
}

// Read from the underlying channel and return a block header that implements the EVMHeader interface
func (s *GnosisNewBlock) Read() (bchain.EVMHeader, bool) {
h, ok := <-s.channel
return &GnosisHeader{Header: h}, ok
}

// Close the underlying channel
func (s *GnosisNewBlock) Close() {
close(s.channel)
}

// GnosisNewTx wraps a transaction hash channel to implement the EVMNewTxSubscriber interface
type GnosisNewTx struct {
channel chan common.Hash
}

// NewGnosisNewTx returns an initialized GnosisNewTx struct
func NewGnosisNewTx() *GnosisNewTx {
return &GnosisNewTx{channel: make(chan common.Hash)}
}

// Channel returns the underlying channel as an empty interface
func (s *GnosisNewTx) Channel() interface{} {
return s.channel
}

// Read from the underlying channel and return a transaction hash that implements the EVMHash interface
func (s *GnosisNewTx) Read() (bchain.EVMHash, bool) {
h, ok := <-s.channel
return &GnosisHash{Hash: h}, ok
}

// Close the underlying channel
func (s *GnosisNewTx) Close() {
close(s.channel)
}
184 changes: 184 additions & 0 deletions bchain/coins/gnosis/gnosisrpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package gnosis

import (
"context"
"encoding/json"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/trezor/blockbook/bchain"
"github.com/trezor/blockbook/bchain/coins/eth"
)

const (
// MainNet is production network
MainNet eth.Network = 100
)

// GnosisRPC is an interface to JSON-RPC bsc service.
type GnosisRPC struct {
*eth.EthereumRPC
}

// NewGnosisRPC returns new GnosisRPC instance.
func NewGnosisRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
c, err := eth.NewEthereumRPC(config, pushHandler)
if err != nil {
return nil, err
}

s := &GnosisRPC{
EthereumRPC: c.(*eth.EthereumRPC),
}

// use trace_block for internal data (debug_trace memory overhead is too expensive)
c.(*eth.EthereumRPC).GetInternalDataForBlock = s.getInternalDataForBlock

return s, nil
}

// Initialize GnosisRPC interface
func (b *GnosisRPC) Initialize() error {
b.OpenRPC = func(url string) (bchain.EVMRPCClient, bchain.EVMClient, error) {
r, err := rpc.Dial(url)
if err != nil {
return nil, nil, err
}
rc := &GnosisRPCClient{Client: r}
ec := &GnosisClient{Client: ethclient.NewClient(r), GnosisRPCClient: rc}
return rc, ec, nil
}

rc, ec, err := b.OpenRPC(b.ChainConfig.RPCURL)
if err != nil {
return err
}

// set chain specific
b.Client = ec
b.RPC = rc
b.MainNetChainID = MainNet
b.NewBlock = &GnosisNewBlock{channel: make(chan *Header)}
b.NewTx = &GnosisNewTx{channel: make(chan common.Hash)}

ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()

id, err := b.Client.NetworkID(ctx)
if err != nil {
return err
}

// parameters for getInfo request
switch eth.Network(id.Uint64()) {
case MainNet:
b.Testnet = false
b.Network = "livenet"
default:
return errors.Errorf("Unknown network id %v", id)
}

glog.Info("rpc: block chain ", b.Network)

return nil
}

type action struct {
Author string `json:"author"`
CallType string `json:"callType"`
From string `json:"from"`
Gas string `json:"gas"`
Input string `json:"input"`
RewardType string `json:"rewardType"`
To string `json:"to"`
Value string `json:"value"`
}

type traceBlockResult struct {
Action action `json:"action"`
BlockHash string `json:"blockHash"`
BlockNumber int `json:"blockNumber"`
Error string `json:"error"`
Result struct {
GasUsed string `json:"gasUsed"`
Output string `json:"output"`
} `json:"result"`
Subtraces int `json:"subtraces"`
TraceAddress []int `json:"traceAddress"`
TransactionHash string `json:"transactionHash"`
TransactionPosition int `json:"transactionPosition"`
Type string `json:"type"`
}

// getInternalDataForBlock extracts internal transfers and creation or destruction of contracts using the parity trace module
func (b *GnosisRPC) getInternalDataForBlock(blockHash string, blockHeight uint32, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, []bchain.ContractInfo, error) {
data := make([]bchain.EthereumInternalData, len(transactions))
contracts := make([]bchain.ContractInfo, 0)
if b.EthereumRPC.ChainConfig.ProcessInternalTransactions {
var n big.Int
n.SetUint64(uint64(blockHeight))
ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
defer cancel()
var trace []traceBlockResult
err := b.RPC.CallContext(ctx, &trace, "trace_block", bchain.ToBlockNumArg(&n))
if err != nil {
glog.Error("trace_block ", blockHash, ", error ", err)
return data, contracts, err
}
for _, t := range trace {
// initiating call does not have any trace addresses and is not an internal transfer
if len(t.TraceAddress) == 0 {
continue
}
d := &data[t.TransactionPosition]
action := t.Action
callType := strings.ToUpper(action.CallType)
traceType := strings.ToUpper(t.Type)
value, err := hexutil.DecodeBig(action.Value)
if traceType == "CREATE" || traceType == "CREATE2" {
d.Type = bchain.CREATE
d.Contract = action.To
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Type: bchain.CREATE,
Value: *value,
From: action.From,
To: action.To, // new contract address
})
contracts = append(contracts, *b.GetCreationContractInfo(d.Contract, blockHeight))
} else if t.Type == "SELFDESTRUCT" {
d.Type = bchain.SELFDESTRUCT
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Type: bchain.SELFDESTRUCT,
Value: *value,
From: action.From, // destroyed contract address
To: action.To,
})
contracts = append(contracts, bchain.ContractInfo{Contract: action.From, DestructedInBlock: blockHeight})
} else if callType == "DELEGATECALL" {
// ignore DELEGATECALL (geth v1.11 the changed tracer behavior)
// https://github.com/ethereum/go-ethereum/issues/26726
} else if t.Type == "REWARD" {
// ignore REWARD as block rewards are not associated with any specific transaction
} else if err == nil && (value.BitLen() > 0 || b.ChainConfig.ProcessZeroInternalTransactions) {
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Value: *value,
From: action.From,
To: action.To,
})
}
if t.Error != "" {
e := eth.PackInternalTransactionError(t.Error)
if len(e) > 1 {
d.Error = strings.ToUpper(e[:1]) + e[1:] + ". "
}
}
}
}
return data, contracts, nil
}
Loading