package eth import ( "blockbook/bchain" "encoding/hex" "encoding/json" "math/big" "strconv" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/protobuf/proto" "github.com/juju/errors" ) // EthereumParser handle type EthereumParser struct { *bchain.BaseParser } // NewEthereumParser returns new EthereumParser instance func NewEthereumParser() *EthereumParser { return &EthereumParser{&bchain.BaseParser{ BlockAddressesToKeep: 0, AmountDecimalPoint: 18, }} } type rpcTransaction struct { AccountNonce string `json:"nonce" gencodec:"required"` Price string `json:"gasPrice" gencodec:"required"` GasLimit string `json:"gas" gencodec:"required"` To string `json:"to" rlp:"nil"` // nil means contract creation Value string `json:"value" gencodec:"required"` Payload string `json:"input" gencodec:"required"` Hash ethcommon.Hash `json:"hash" rlp:"-"` BlockNumber string `json:"blockNumber"` BlockHash *ethcommon.Hash `json:"blockHash,omitempty"` From string `json:"from"` TransactionIndex string `json:"transactionIndex"` // Signature values V string `json:"v" gencodec:"required"` R string `json:"r" gencodec:"required"` S string `json:"s" gencodec:"required"` } type rpcBlock struct { Hash ethcommon.Hash `json:"hash"` Transactions []rpcTransaction `json:"transactions"` UncleHashes []ethcommon.Hash `json:"uncles"` } func ethHashToHash(h ethcommon.Hash) string { return h.Hex() } func ethNumber(n string) (int64, error) { if len(n) > 2 { return strconv.ParseInt(n[2:], 16, 64) } return 0, errors.Errorf("Not a number: '%v'", n) } func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, blocktime int64, confirmations uint32) (*bchain.Tx, error) { txid := ethHashToHash(tx.Hash) var ( fa, ta []string err error ) if len(tx.From) > 2 { fa = []string{tx.From} } if len(tx.To) > 2 { ta = []string{tx.To} } // temporarily, the complete rpcTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex bh := tx.BlockHash tx.BlockHash = nil b, err := json.Marshal(tx) if err != nil { return nil, err } tx.BlockHash = bh h := hex.EncodeToString(b) vs, err := hexutil.DecodeBig(tx.Value) if err != nil { return nil, err } return &bchain.Tx{ Blocktime: blocktime, Confirmations: confirmations, Hex: h, // LockTime Time: blocktime, Txid: txid, Vin: []bchain.Vin{ { Addresses: fa, // Coinbase // ScriptSig // Sequence // Txid // Vout }, }, Vout: []bchain.Vout{ { N: 0, // there is always up to one To address ValueSat: *vs, ScriptPubKey: bchain.ScriptPubKey{ // Hex Addresses: ta, }, }, }, }, nil } // GetAddrDescFromVout returns internal address representation of given transaction output func (p *EthereumParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) { if len(output.ScriptPubKey.Addresses) != 1 { return nil, bchain.ErrAddressMissing } return p.GetAddrDescFromAddress(output.ScriptPubKey.Addresses[0]) } func has0xPrefix(s string) bool { return len(s) >= 2 && s[0] == '0' && (s[1]|32) == 'x' } // GetAddrDescFromAddress returns internal address representation of given address func (p *EthereumParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) { // github.com/ethereum/go-ethereum/common.HexToAddress does not handle address errors, using own decoding if has0xPrefix(address) { address = address[2:] } if len(address) == 0 { return nil, bchain.ErrAddressMissing } if len(address)&1 == 1 { address = "0" + address } return hex.DecodeString(address) } // GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable func (p *EthereumParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) { return []string{hexutil.Encode(addrDesc)}, true, nil } // GetScriptFromAddrDesc returns output script for given address descriptor func (p *EthereumParser) GetScriptFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]byte, error) { return addrDesc, nil } func hexDecode(s string) ([]byte, error) { b, err := hexutil.Decode(s) if err != nil && err != hexutil.ErrEmptyString { return nil, err } return b, nil } func hexDecodeBig(s string) ([]byte, error) { b, err := hexutil.DecodeBig(s) if err != nil { return nil, err } return b.Bytes(), nil } func hexEncodeBig(b []byte) string { var i big.Int i.SetBytes(b) return hexutil.EncodeBig(&i) } // PackTx packs transaction to byte array func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) { b, err := hex.DecodeString(tx.Hex) if err != nil { return nil, err } var r rpcTransaction var n uint64 err = json.Unmarshal(b, &r) if err != nil { return nil, err } pt := &ProtoTransaction{} if pt.AccountNonce, err = hexutil.DecodeUint64(r.AccountNonce); err != nil { return nil, errors.Annotatef(err, "AccountNonce %v", r.AccountNonce) } if n, err = hexutil.DecodeUint64(r.BlockNumber); err != nil { return nil, errors.Annotatef(err, "BlockNumber %v", r.BlockNumber) } pt.BlockNumber = uint32(n) pt.BlockTime = uint64(blockTime) if pt.From, err = hexDecode(r.From); err != nil { return nil, errors.Annotatef(err, "From %v", r.From) } if pt.GasLimit, err = hexutil.DecodeUint64(r.GasLimit); err != nil { return nil, errors.Annotatef(err, "GasLimit %v", r.GasLimit) } pt.Hash = r.Hash.Bytes() if pt.Payload, err = hexDecode(r.Payload); err != nil { return nil, errors.Annotatef(err, "Payload %v", r.Payload) } if pt.Price, err = hexDecodeBig(r.Price); err != nil { return nil, errors.Annotatef(err, "Price %v", r.Price) } if pt.R, err = hexDecodeBig(r.R); err != nil { return nil, errors.Annotatef(err, "R %v", r.R) } if pt.S, err = hexDecodeBig(r.S); err != nil { return nil, errors.Annotatef(err, "S %v", r.S) } if pt.V, err = hexDecodeBig(r.V); err != nil { return nil, errors.Annotatef(err, "V %v", r.V) } if pt.To, err = hexDecode(r.To); err != nil { return nil, errors.Annotatef(err, "To %v", r.To) } if n, err = hexutil.DecodeUint64(r.TransactionIndex); err != nil { return nil, errors.Annotatef(err, "TransactionIndex %v", r.TransactionIndex) } pt.TransactionIndex = uint32(n) if pt.Value, err = hexDecodeBig(r.Value); err != nil { return nil, errors.Annotatef(err, "Value %v", r.Value) } return proto.Marshal(pt) } // UnpackTx unpacks transaction from byte array func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { var pt ProtoTransaction err := proto.Unmarshal(buf, &pt) if err != nil { return nil, 0, err } r := rpcTransaction{ AccountNonce: hexutil.EncodeUint64(pt.AccountNonce), BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)), From: hexutil.Encode(pt.From), GasLimit: hexutil.EncodeUint64(pt.GasLimit), Hash: ethcommon.BytesToHash(pt.Hash), Payload: hexutil.Encode(pt.Payload), Price: hexEncodeBig(pt.Price), R: hexEncodeBig(pt.R), S: hexEncodeBig(pt.S), V: hexEncodeBig(pt.V), To: hexutil.Encode(pt.To), TransactionIndex: hexutil.EncodeUint64(uint64(pt.TransactionIndex)), Value: hexEncodeBig(pt.Value), } tx, err := p.ethTxToTx(&r, int64(pt.BlockTime), 0) if err != nil { return nil, 0, err } return tx, pt.BlockNumber, nil } // PackedTxidLen returns length in bytes of packed txid func (p *EthereumParser) PackedTxidLen() int { return 32 } // PackTxid packs txid to byte array func (p *EthereumParser) PackTxid(txid string) ([]byte, error) { if has0xPrefix(txid) { txid = txid[2:] } return hex.DecodeString(txid) } // UnpackTxid unpacks byte array to txid func (p *EthereumParser) UnpackTxid(buf []byte) (string, error) { return hexutil.Encode(buf), nil } // PackBlockHash packs block hash to byte array func (p *EthereumParser) PackBlockHash(hash string) ([]byte, error) { if has0xPrefix(hash) { hash = hash[2:] } return hex.DecodeString(hash) } // UnpackBlockHash unpacks byte array to block hash func (p *EthereumParser) UnpackBlockHash(buf []byte) (string, error) { return hexutil.Encode(buf), nil } // GetChainType returns TypeEthereum func (p *EthereumParser) GetChainType() bchain.ChainType { return bchain.ChainEthereumType }