github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/vent/chain/ethereum/ethereum.go (about) 1 package ethereum 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 "strconv" 9 "time" 10 11 "github.com/hyperledger/burrow/binary" 12 "github.com/hyperledger/burrow/crypto" 13 "github.com/hyperledger/burrow/encoding/web3hex" 14 "github.com/hyperledger/burrow/event" 15 "github.com/hyperledger/burrow/event/query" 16 "github.com/hyperledger/burrow/execution/errors" 17 "github.com/hyperledger/burrow/execution/exec" 18 "github.com/hyperledger/burrow/logging" 19 "github.com/hyperledger/burrow/rpc/rpcevents" 20 "github.com/hyperledger/burrow/rpc/web3/ethclient" 21 "github.com/hyperledger/burrow/vent/chain" 22 "github.com/hyperledger/burrow/vent/types" 23 "google.golang.org/grpc/connectivity" 24 ) 25 26 const Scope = "Ethereum" 27 28 type Chain struct { 29 client ThrottleClient 30 filter *chain.Filter 31 chainID string 32 version string 33 consumerConfig *chain.BlockConsumerConfig 34 logger *logging.Logger 35 } 36 37 var _ chain.Chain = (*Chain)(nil) 38 39 type EthClient interface { 40 GetLogs(filter *ethclient.Filter) ([]*ethclient.EthLog, error) 41 BlockNumber() (uint64, error) 42 GetBlockByNumber(height string) (*ethclient.Block, error) 43 NetVersion() (string, error) 44 Web3ClientVersion() (string, error) 45 Syncing() (bool, error) 46 } 47 48 // We rely on this failing if the chain is not an Ethereum Chain 49 func New(client EthClient, filter *chain.Filter, consumerConfig *chain.BlockConsumerConfig, 50 logger *logging.Logger) (*Chain, error) { 51 logger = logger.WithScope(Scope) 52 throttleClient := NewThrottleClient(client, consumerConfig.MaxRequests, consumerConfig.TimeBase, logger) 53 chainID, err := throttleClient.NetVersion() 54 if err != nil { 55 return nil, fmt.Errorf("could not get Ethereum ChainID: %w", err) 56 } 57 version, err := throttleClient.Web3ClientVersion() 58 if err != nil { 59 return nil, fmt.Errorf("could not get Ethereum node version: %w", err) 60 } 61 return &Chain{ 62 client: throttleClient, 63 filter: filter, 64 chainID: chainID, 65 version: version, 66 consumerConfig: consumerConfig, 67 logger: logger, 68 }, nil 69 } 70 71 func (c *Chain) StatusMessage(ctx context.Context, lastProcessedHeight uint64) []interface{} { 72 // TODO: more info is available from web3 73 return []interface{}{ 74 "msg", "status", 75 "chain_type", "Ethereum", 76 "last_processed_height", lastProcessedHeight, 77 } 78 } 79 80 func (c *Chain) GetABI(ctx context.Context, address crypto.Address) (string, error) { 81 // Unsupported by Ethereum 82 return "", nil 83 } 84 85 func (c *Chain) GetVersion() string { 86 return c.version 87 } 88 89 func (c *Chain) GetChainID() string { 90 return c.chainID 91 } 92 93 func (c *Chain) ConsumeBlocks(ctx context.Context, in *rpcevents.BlockRange, consumer func(chain.Block) error) error { 94 return Consume(c.client, c.filter, in, c.consumerConfig, c.logger, consumer) 95 } 96 97 func (c *Chain) Connectivity() connectivity.State { 98 // TODO: better connectivity information 99 _, err := c.client.Syncing() 100 if err != nil { 101 return connectivity.TransientFailure 102 } 103 return connectivity.Ready 104 } 105 106 func (c *Chain) Close() error { 107 // just a http.Client - nothing to free 108 return nil 109 } 110 111 type Block struct { 112 client EthClient 113 Height uint64 114 Transactions []chain.Transaction 115 } 116 117 func newBlock(client EthClient, log *Event) *Block { 118 return &Block{ 119 client: client, 120 Height: log.Height, 121 Transactions: []chain.Transaction{NewEthereumTransaction(log)}, 122 } 123 } 124 125 var _ chain.Block = (*Block)(nil) 126 127 func (b *Block) GetHeight() uint64 { 128 return b.Height 129 } 130 131 func (b *Block) GetTxs() []chain.Transaction { 132 return b.Transactions 133 } 134 135 func (b *Block) GetMetadata(columns types.SQLColumnNames) (map[string]interface{}, error) { 136 block, err := b.client.GetBlockByNumber(web3hex.Encoder.Uint64(b.Height)) 137 if err != nil { 138 return nil, err 139 } 140 d := new(web3hex.Decoder) 141 blockHeader, err := json.Marshal(block) 142 if err != nil { 143 return nil, fmt.Errorf("could not serialise block header: %w", err) 144 } 145 return map[string]interface{}{ 146 columns.Height: strconv.FormatUint(b.Height, 10), 147 columns.TimeStamp: time.Unix(d.Int64(block.Timestamp), 0), 148 columns.BlockHeader: string(blockHeader), 149 }, d.Err() 150 } 151 152 func (b *Block) appendTransaction(log *Event) { 153 b.Transactions = append(b.Transactions, &Transaction{ 154 Index: uint64(len(b.Transactions)), 155 Hash: log.TransactionHash, 156 Events: []chain.Event{log}, 157 }) 158 } 159 160 func (b *Block) appendEvent(log *Event) { 161 tx := b.Transactions[len(b.Transactions)-1].(*Transaction) 162 log.Index = uint64(len(tx.Events)) 163 tx.Events = append(tx.Events, log) 164 } 165 166 type Transaction struct { 167 Height uint64 168 Index uint64 169 Hash binary.HexBytes 170 Events []chain.Event 171 } 172 173 func NewEthereumTransaction(log *Event) *Transaction { 174 return &Transaction{ 175 Height: log.Height, 176 Index: 0, 177 Hash: log.TransactionHash, 178 Events: []chain.Event{log}, 179 } 180 } 181 182 func (tx *Transaction) GetHash() binary.HexBytes { 183 return tx.Hash 184 } 185 186 func (tx *Transaction) GetIndex() uint64 { 187 return tx.Index 188 } 189 190 func (tx *Transaction) GetEvents() []chain.Event { 191 return tx.Events 192 } 193 194 func (tx *Transaction) GetException() *errors.Exception { 195 // Ethereum does not retain an log from reverted transactions 196 return nil 197 } 198 199 func (tx *Transaction) GetOrigin() *chain.Origin { 200 // Origin refers to a previous dumped chain which is not a concept in Ethereum 201 return nil 202 } 203 204 func (tx *Transaction) GetMetadata(columns types.SQLColumnNames) (map[string]interface{}, error) { 205 return map[string]interface{}{ 206 columns.Height: tx.Height, 207 columns.TxHash: tx.Hash.String(), 208 columns.TxIndex: tx.Index, 209 columns.TxType: exec.TypeLog.String(), 210 }, nil 211 } 212 213 var _ chain.Transaction = (*Transaction)(nil) 214 215 type Event struct { 216 exec.LogEvent 217 Height uint64 218 // Index of event in entire block (what ethereum provides us with 219 IndexInBlock uint64 220 // Index of event in transaction 221 Index uint64 222 TransactionHash binary.HexBytes 223 } 224 225 var _ chain.Event = (*Event)(nil) 226 227 func newEvent(log *ethclient.EthLog) (*Event, error) { 228 d := new(web3hex.Decoder) 229 topics := make([]binary.Word256, len(log.Topics)) 230 for i, t := range log.Topics { 231 topics[i] = binary.LeftPadWord256(d.Bytes(t)) 232 } 233 txHash := d.Bytes(log.TransactionHash) 234 return &Event{ 235 LogEvent: exec.LogEvent{ 236 Topics: topics, 237 Address: d.Address(log.Address), 238 Data: d.Bytes(log.Data), 239 }, 240 Height: d.Uint64(log.BlockNumber), 241 IndexInBlock: d.Uint64(log.LogIndex), 242 TransactionHash: txHash, 243 }, d.Err() 244 } 245 246 func (ev *Event) GetIndex() uint64 { 247 return ev.Index 248 } 249 250 func (ev *Event) GetTransactionHash() binary.HexBytes { 251 return ev.TransactionHash 252 } 253 254 func (ev *Event) GetAddress() crypto.Address { 255 return ev.Address 256 } 257 258 func (ev *Event) GetTopics() []binary.Word256 { 259 return ev.Topics 260 } 261 262 func (ev *Event) GetData() []byte { 263 return ev.Data 264 } 265 266 func (ev *Event) Get(key string) (value interface{}, ok bool) { 267 switch key { 268 case event.EventTypeKey: 269 return exec.TypeLog, true 270 } 271 v, ok := ev.LogEvent.Get(key) 272 if ok { 273 return v, ok 274 } 275 return query.GetReflect(reflect.ValueOf(ev), key) 276 }