Parse ethereum input data
This commit is contained in:
parent
0ccb9b37b4
commit
6fdf6e297c
@ -207,6 +207,7 @@ type EthereumSpecific struct {
|
||||
GasUsed *big.Int `json:"gasUsed"`
|
||||
GasPrice *Amount `json:"gasPrice"`
|
||||
Data string `json:"data,omitempty"`
|
||||
ParsedData *bchain.EthereumParsedInputData `json:"parsedData,omitempty"`
|
||||
InternalTransfers []EthereumInternalTransfer `json:"internalTransfers,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@ -127,6 +127,23 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool
|
||||
return w.GetTransactionFromBchainTx(bchainTx, height, spendingTxs, specificJSON)
|
||||
}
|
||||
|
||||
func (w *Worker) getParsedEthereumInputData(data string) *bchain.EthereumParsedInputData {
|
||||
var err error
|
||||
var signatures *[]bchain.FourByteSignature
|
||||
fourBytes := eth.GetSignatureFromData(data)
|
||||
if fourBytes != 0 {
|
||||
signatures, err = w.db.GetFourByteSignatures(fourBytes)
|
||||
if err != nil {
|
||||
glog.Errorf("GetFourByteSignatures(%v) error %v", fourBytes, err)
|
||||
return nil
|
||||
}
|
||||
if signatures == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return eth.ParseInputData(signatures, data)
|
||||
}
|
||||
|
||||
// GetTransactionFromBchainTx reads transaction data from txid
|
||||
func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spendingTxs bool, specificJSON bool) (*Tx, error) {
|
||||
var err error
|
||||
@ -270,6 +287,8 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
}
|
||||
}
|
||||
|
||||
parsedInputData := w.getParsedEthereumInputData(ethTxData.Data)
|
||||
|
||||
// mempool txs do not have fees yet
|
||||
if ethTxData.GasUsed != nil {
|
||||
feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed)
|
||||
@ -278,12 +297,13 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
valOutSat = bchainTx.Vout[0].ValueSat
|
||||
}
|
||||
ethSpecific = &EthereumSpecific{
|
||||
GasLimit: ethTxData.GasLimit,
|
||||
GasPrice: (*Amount)(ethTxData.GasPrice),
|
||||
GasUsed: ethTxData.GasUsed,
|
||||
Nonce: ethTxData.Nonce,
|
||||
Status: ethTxData.Status,
|
||||
Data: ethTxData.Data,
|
||||
GasLimit: ethTxData.GasLimit,
|
||||
GasPrice: (*Amount)(ethTxData.GasPrice),
|
||||
GasUsed: ethTxData.GasUsed,
|
||||
Nonce: ethTxData.Nonce,
|
||||
Status: ethTxData.Status,
|
||||
Data: ethTxData.Data,
|
||||
ParsedData: parsedInputData,
|
||||
}
|
||||
if internalData != nil {
|
||||
ethSpecific.Type = internalData.Type
|
||||
@ -674,7 +694,6 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
|
||||
}
|
||||
|
||||
t := Token{
|
||||
Type: ERC20TokenType,
|
||||
Contract: ci.Contract,
|
||||
Name: ci.Name,
|
||||
Symbol: ci.Symbol,
|
||||
@ -685,6 +704,7 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
|
||||
// return contract balances/values only at or above AccountDetailsTokenBalances
|
||||
if details >= AccountDetailsTokenBalances && validContract {
|
||||
if c.Type == bchain.ERC20 {
|
||||
t.Type = ERC20TokenType
|
||||
// get Erc20 Contract Balance from blockchain, balance obtained from adding and subtracting transfers is not correct
|
||||
b, err := w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
|
||||
if err != nil {
|
||||
@ -694,15 +714,20 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
|
||||
t.BalanceSat = (*Amount)(b)
|
||||
}
|
||||
} else {
|
||||
if len(t.Ids) > 0 {
|
||||
ids := make([]Amount, len(t.Ids))
|
||||
if c.Type == bchain.ERC721 {
|
||||
t.Type = ERC771TokenType
|
||||
} else {
|
||||
t.Type = ERC1155TokenType
|
||||
}
|
||||
if len(c.Ids) > 0 {
|
||||
ids := make([]Amount, len(c.Ids))
|
||||
for j := range ids {
|
||||
ids[j] = (Amount)(c.Ids[j])
|
||||
}
|
||||
t.Ids = ids
|
||||
}
|
||||
if len(t.IdValues) > 0 {
|
||||
idValues := make([]TokenTransferValues, len(t.IdValues))
|
||||
if len(c.IdValues) > 0 {
|
||||
idValues := make([]TokenTransferValues, len(c.IdValues))
|
||||
for j := range idValues {
|
||||
idValues[j].Id = (*Amount)(&c.IdValues[j].Id)
|
||||
idValues[j].Value = (*Amount)(&c.IdValues[j].Value)
|
||||
|
||||
@ -4,8 +4,15 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/golang/glog"
|
||||
"github.com/trezor/blockbook/bchain"
|
||||
)
|
||||
|
||||
func parseSimpleNumericProperty(data string) *big.Int {
|
||||
@ -58,15 +65,230 @@ func parseSimpleStringProperty(data string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func Decamel(s string) string {
|
||||
func decamel(s string) string {
|
||||
var b bytes.Buffer
|
||||
splittable := false
|
||||
for _, v := range s {
|
||||
if splittable && unicode.IsUpper(v) {
|
||||
b.WriteByte(' ')
|
||||
for i, v := range s {
|
||||
if i == 0 {
|
||||
b.WriteRune(unicode.ToUpper(v))
|
||||
} else {
|
||||
if splittable && unicode.IsUpper(v) {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteRune(v)
|
||||
splittable = unicode.IsLower(v) || unicode.IsNumber(v)
|
||||
}
|
||||
b.WriteRune(v)
|
||||
splittable = unicode.IsLower(v) || unicode.IsNumber(v)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func GetSignatureFromData(data string) uint32 {
|
||||
if has0xPrefix(data) {
|
||||
data = data[2:]
|
||||
}
|
||||
if len(data) < 8 {
|
||||
return 0
|
||||
}
|
||||
sig, err := strconv.ParseUint(data[:8], 16, 32)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return uint32(sig)
|
||||
}
|
||||
|
||||
const ErrorTy byte = 255
|
||||
|
||||
func processParam(data string, index int, t *abi.Type, processed []bool) ([]string, int, bool) {
|
||||
var retval []string
|
||||
d := index << 6
|
||||
if d+64 > len(data) {
|
||||
return nil, 0, false
|
||||
}
|
||||
block := data[d : d+64]
|
||||
switch t.T {
|
||||
// static types
|
||||
case abi.IntTy, abi.UintTy, abi.BoolTy:
|
||||
var n big.Int
|
||||
_, ok := n.SetString(block, 16)
|
||||
if !ok {
|
||||
return nil, 0, false
|
||||
}
|
||||
if t.T == abi.BoolTy {
|
||||
if n.Int64() != 0 {
|
||||
retval = []string{"true"}
|
||||
} else {
|
||||
retval = []string{"false"}
|
||||
}
|
||||
} else {
|
||||
retval = []string{n.String()}
|
||||
}
|
||||
processed[index] = true
|
||||
index++
|
||||
case abi.AddressTy:
|
||||
b, err := hex.DecodeString(block[24:])
|
||||
if err != nil {
|
||||
return nil, 0, false
|
||||
}
|
||||
retval = []string{EIP55Address(b)}
|
||||
processed[index] = true
|
||||
index++
|
||||
case abi.FixedBytesTy:
|
||||
retval = []string{"0x" + block[:t.Size<<1]}
|
||||
processed[index] = true
|
||||
index++
|
||||
case abi.ArrayTy:
|
||||
for i := 0; i < t.Size; i++ {
|
||||
var r []string
|
||||
var ok bool
|
||||
r, index, ok = processParam(data, index, t.Elem, processed)
|
||||
if !ok {
|
||||
return nil, 0, false
|
||||
}
|
||||
retval = append(retval, r...)
|
||||
}
|
||||
// dynamic types
|
||||
case abi.StringTy, abi.BytesTy, abi.SliceTy:
|
||||
// get offset of dynamic type
|
||||
offset, err := strconv.ParseInt(block, 16, 64)
|
||||
if err != nil {
|
||||
return nil, 0, false
|
||||
}
|
||||
processed[index] = true
|
||||
index++
|
||||
offset <<= 1
|
||||
d = int(offset)
|
||||
dynIndex := d >> 6
|
||||
if d+64 > len(data) || d < 0 {
|
||||
return nil, 0, false
|
||||
}
|
||||
// get element count of dynamic type
|
||||
c, err := strconv.ParseInt(data[d:d+64], 16, 64)
|
||||
count := int(c)
|
||||
if err != nil {
|
||||
return nil, 0, false
|
||||
}
|
||||
processed[dynIndex] = true
|
||||
dynIndex++
|
||||
if t.T == abi.StringTy || t.T == abi.BytesTy {
|
||||
d += 64
|
||||
de := d + (count << 1)
|
||||
if de > len(data) {
|
||||
return nil, 0, false
|
||||
}
|
||||
if count == 0 {
|
||||
retval = []string{""}
|
||||
} else {
|
||||
block = data[d:de]
|
||||
if t.T == abi.StringTy {
|
||||
b, err := hex.DecodeString(block)
|
||||
if err != nil {
|
||||
return nil, 0, false
|
||||
}
|
||||
retval = []string{string(b)}
|
||||
} else {
|
||||
retval = []string{"0x" + block}
|
||||
}
|
||||
count = ((count - 1) >> 5) + 1
|
||||
for i := 0; i < count; i++ {
|
||||
processed[dynIndex] = true
|
||||
dynIndex++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < count; i++ {
|
||||
var r []string
|
||||
var ok bool
|
||||
r, dynIndex, ok = processParam(data, dynIndex, t.Elem, processed)
|
||||
if !ok {
|
||||
return nil, 0, false
|
||||
}
|
||||
retval = append(retval, r...)
|
||||
}
|
||||
}
|
||||
// types not processed
|
||||
case abi.HashTy, abi.FixedPointTy, abi.FunctionTy, abi.TupleTy:
|
||||
fallthrough
|
||||
default:
|
||||
return nil, 0, false
|
||||
}
|
||||
return retval, index, true
|
||||
}
|
||||
|
||||
func tryParseParams(data string, params []string, parsedParams []abi.Type) []bchain.EthereumParsedInputParam {
|
||||
processed := make([]bool, len(data)/64)
|
||||
parsed := make([]bchain.EthereumParsedInputParam, len(params))
|
||||
index := 0
|
||||
var values []string
|
||||
var ok bool
|
||||
for i := range params {
|
||||
t := &parsedParams[i]
|
||||
values, index, ok = processParam(data, index, t, processed)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
parsed[i] = bchain.EthereumParsedInputParam{Type: params[i], Values: values}
|
||||
}
|
||||
// all data must be processed, otherwise wrong signature
|
||||
for _, p := range processed {
|
||||
if !p {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// ParseInputData tries to parse transaction input data from known FourByteSignatures
|
||||
// as there may be multiple signatures for the same four bytes, it tries to match the input to the known parameters
|
||||
// it does not parse tuples for now
|
||||
func ParseInputData(signatures *[]bchain.FourByteSignature, data string) *bchain.EthereumParsedInputData {
|
||||
if len(data) <= 2 { // data is empty or 0x
|
||||
return &bchain.EthereumParsedInputData{Name: "Transfer"}
|
||||
}
|
||||
if len(data) < 10 || (len(data)-10)%64 != 0 {
|
||||
return nil
|
||||
}
|
||||
parsed := bchain.EthereumParsedInputData{
|
||||
MethodId: data[:10],
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
glog.Error("ParseInputData recovered from panic: ", r, ", ", data, ",signatures ", signatures)
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
if signatures != nil {
|
||||
data = data[10:]
|
||||
for i := range *signatures {
|
||||
s := &(*signatures)[i]
|
||||
// if not yet done, set DecamelName and Function and parse parameter types from string to abi.Type
|
||||
// the signatures are stored in cache
|
||||
if s.DecamelName == "" {
|
||||
s.DecamelName = decamel(s.Name)
|
||||
s.Function = s.Name + "(" + strings.Join(s.Parameters, ", ") + ")"
|
||||
s.ParsedParameters = make([]abi.Type, len(s.Parameters))
|
||||
for j := range s.Parameters {
|
||||
var t abi.Type
|
||||
if len(s.Parameters[j]) > 0 && s.Parameters[j][0] == '(' {
|
||||
// Tuple type is not supported for now
|
||||
t = abi.Type{T: abi.TupleTy}
|
||||
} else {
|
||||
var err error
|
||||
t, err = abi.NewType(s.Parameters[j], "", nil)
|
||||
if err != nil {
|
||||
t = abi.Type{T: ErrorTy}
|
||||
}
|
||||
}
|
||||
s.ParsedParameters[j] = t
|
||||
}
|
||||
}
|
||||
parsedParams := tryParseParams(data, s.Parameters, s.ParsedParameters)
|
||||
if parsedParams != nil {
|
||||
parsed.Name = s.DecamelName
|
||||
parsed.Function = s.Function
|
||||
parsed.Params = parsedParams
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return &parsed
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,6 +1,10 @@
|
||||
package bchain
|
||||
|
||||
import "math/big"
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
)
|
||||
|
||||
// EthereumType specific
|
||||
|
||||
@ -12,6 +16,27 @@ type EthereumInternalTransfer struct {
|
||||
Value big.Int `json:"value"`
|
||||
}
|
||||
|
||||
type FourByteSignature struct {
|
||||
// stored in DB
|
||||
Name string
|
||||
Parameters []string
|
||||
// processed from DB data and stored only in cache
|
||||
DecamelName string
|
||||
Function string
|
||||
ParsedParameters []abi.Type
|
||||
}
|
||||
|
||||
type EthereumParsedInputParam struct {
|
||||
Type string `json:"type"`
|
||||
Values []string `json:"values,omitempty"`
|
||||
}
|
||||
type EthereumParsedInputData struct {
|
||||
MethodId string `json:"methodId"`
|
||||
Name string `json:"name"`
|
||||
Function string `json:"function,omitempty"`
|
||||
Params []EthereumParsedInputParam `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
// EthereumInternalTransactionType - type of ethereum transaction from internal data
|
||||
type EthereumInternalTransactionType int
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/flier/gorocksdb"
|
||||
"github.com/golang/glog"
|
||||
@ -578,11 +579,8 @@ func (d *RocksDB) unpackEthInternalData(buf []byte) (*bchain.EthereumInternalDat
|
||||
return &id, nil
|
||||
}
|
||||
|
||||
type FourByteSignature struct {
|
||||
Name string
|
||||
Parameters []string
|
||||
}
|
||||
|
||||
// FourByteSignature contains 4byte signature of transaction value with parameters
|
||||
// and parsed parameters (that are not stored in DB)
|
||||
func packFourByteKey(fourBytes uint32, id uint32) []byte {
|
||||
key := make([]byte, 0, 8)
|
||||
key = append(key, packUint(fourBytes)...)
|
||||
@ -590,7 +588,7 @@ func packFourByteKey(fourBytes uint32, id uint32) []byte {
|
||||
return key
|
||||
}
|
||||
|
||||
func packFourByteSignature(signature *FourByteSignature) []byte {
|
||||
func packFourByteSignature(signature *bchain.FourByteSignature) []byte {
|
||||
buf := packString(signature.Name)
|
||||
for i := range signature.Parameters {
|
||||
buf = append(buf, packString(signature.Parameters[i])...)
|
||||
@ -598,8 +596,8 @@ func packFourByteSignature(signature *FourByteSignature) []byte {
|
||||
return buf
|
||||
}
|
||||
|
||||
func unpackFourByteSignature(buf []byte) (*FourByteSignature, error) {
|
||||
var signature FourByteSignature
|
||||
func unpackFourByteSignature(buf []byte) (*bchain.FourByteSignature, error) {
|
||||
var signature bchain.FourByteSignature
|
||||
var l int
|
||||
signature.Name, l = unpackString(buf)
|
||||
for l < len(buf) {
|
||||
@ -610,7 +608,8 @@ func unpackFourByteSignature(buf []byte) (*FourByteSignature, error) {
|
||||
return &signature, nil
|
||||
}
|
||||
|
||||
func (d *RocksDB) GetFourByteSignature(fourBytes uint32, id uint32) (*FourByteSignature, error) {
|
||||
// GetFourByteSignature gets all 4byte signature of given fourBytes and id
|
||||
func (d *RocksDB) GetFourByteSignature(fourBytes uint32, id uint32) (*bchain.FourByteSignature, error) {
|
||||
key := packFourByteKey(fourBytes, id)
|
||||
val, err := d.db.GetCF(d.ro, d.cfh[cfFunctionSignatures], key)
|
||||
if err != nil {
|
||||
@ -624,12 +623,51 @@ func (d *RocksDB) GetFourByteSignature(fourBytes uint32, id uint32) (*FourByteSi
|
||||
return unpackFourByteSignature(buf)
|
||||
}
|
||||
|
||||
func (d *RocksDB) StoreFourByteSignature(wb *gorocksdb.WriteBatch, fourBytes uint32, id uint32, signature *FourByteSignature) error {
|
||||
var cachedByteSignatures = make(map[uint32]*[]bchain.FourByteSignature)
|
||||
var cachedByteSignaturesMux sync.Mutex
|
||||
|
||||
// GetFourByteSignatures gets all 4byte signatures of given fourBytes
|
||||
// (there may be more than one signature starting with the same four bytes)
|
||||
func (d *RocksDB) GetFourByteSignatures(fourBytes uint32) (*[]bchain.FourByteSignature, error) {
|
||||
cachedByteSignaturesMux.Lock()
|
||||
signatures, found := cachedByteSignatures[fourBytes]
|
||||
cachedByteSignaturesMux.Unlock()
|
||||
if !found {
|
||||
retval := []bchain.FourByteSignature{}
|
||||
key := packUint(fourBytes)
|
||||
it := d.db.NewIteratorCF(d.ro, d.cfh[cfFunctionSignatures])
|
||||
defer it.Close()
|
||||
for it.Seek(key); it.Valid(); it.Next() {
|
||||
current := it.Key().Data()
|
||||
if bytes.Compare(current[:4], key) > 0 {
|
||||
break
|
||||
}
|
||||
val := it.Value().Data()
|
||||
signature, err := unpackFourByteSignature(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retval = append(retval, *signature)
|
||||
}
|
||||
cachedByteSignaturesMux.Lock()
|
||||
cachedByteSignatures[fourBytes] = &retval
|
||||
cachedByteSignaturesMux.Unlock()
|
||||
return &retval, nil
|
||||
}
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
// StoreFourByteSignature stores 4byte signature in DB
|
||||
func (d *RocksDB) StoreFourByteSignature(wb *gorocksdb.WriteBatch, fourBytes uint32, id uint32, signature *bchain.FourByteSignature) error {
|
||||
key := packFourByteKey(fourBytes, id)
|
||||
wb.PutCF(d.cfh[cfFunctionSignatures], key, packFourByteSignature(signature))
|
||||
cachedByteSignaturesMux.Lock()
|
||||
delete(cachedByteSignatures, fourBytes)
|
||||
cachedByteSignaturesMux.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEthereumInternalData gets transaction internal data from DB
|
||||
func (d *RocksDB) GetEthereumInternalData(txid string) (*bchain.EthereumInternalData, error) {
|
||||
btxID, err := d.chainParser.PackTxid(txid)
|
||||
if err != nil {
|
||||
@ -902,7 +940,9 @@ func (d *RocksDB) disconnectAddress(btxID []byte, internal bool, addrDesc bchain
|
||||
glog.Warning("AddressContracts ", addrDesc, ", contract ", contractIndex, " Txs would be negative, tx ", hex.EncodeToString(btxID))
|
||||
}
|
||||
} else {
|
||||
glog.Warning("AddressContracts ", addrDesc, ", contract ", btxContract.contract, " not found, tx ", hex.EncodeToString(btxID))
|
||||
if !isZeroAddress(addrDesc) {
|
||||
glog.Warning("AddressContracts ", addrDesc, ", contract ", btxContract.contract, " not found, tx ", hex.EncodeToString(btxID))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -301,7 +301,7 @@ func formatInternalData(in *bchain.EthereumInternalData) *bchain.EthereumInterna
|
||||
func testFourByteSignature(t *testing.T, d *RocksDB) {
|
||||
fourBytes := uint32(1234123)
|
||||
id := uint32(42313)
|
||||
signature := FourByteSignature{
|
||||
signature := bchain.FourByteSignature{
|
||||
Name: "xyz",
|
||||
Parameters: []string{"address", "(bytes,uint256[],uint256)", "uint16"},
|
||||
}
|
||||
@ -320,6 +320,13 @@ func testFourByteSignature(t *testing.T, d *RocksDB) {
|
||||
if !reflect.DeepEqual(*got, signature) {
|
||||
t.Errorf("testFourByteSignature: got %+v, want %+v", got, signature)
|
||||
}
|
||||
gotSlice, err := d.GetFourByteSignatures(fourBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(*gotSlice, []bchain.FourByteSignature{signature}) {
|
||||
t.Errorf("testFourByteSignature: got %+v, want %+v", *gotSlice, []bchain.FourByteSignature{signature})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRocksDB_Index_EthereumType is an integration test probing the whole indexing functionality for EthereumType chains
|
||||
@ -1165,24 +1172,24 @@ func Test_packUnpackBlockTx(t *testing.T) {
|
||||
func Test_packUnpackFourByteSignature(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
signature FourByteSignature
|
||||
signature bchain.FourByteSignature
|
||||
}{
|
||||
{
|
||||
name: "no params",
|
||||
signature: FourByteSignature{
|
||||
signature: bchain.FourByteSignature{
|
||||
Name: "abcdef",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one param",
|
||||
signature: FourByteSignature{
|
||||
signature: bchain.FourByteSignature{
|
||||
Name: "opqr",
|
||||
Parameters: []string{"uint16"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple params",
|
||||
signature: FourByteSignature{
|
||||
signature: bchain.FourByteSignature{
|
||||
Name: "xyz",
|
||||
Parameters: []string{"address", "(bytes,uint256[],uint256)", "uint16"},
|
||||
},
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/flier/gorocksdb"
|
||||
"github.com/golang/glog"
|
||||
"github.com/trezor/blockbook/bchain"
|
||||
"github.com/trezor/blockbook/db"
|
||||
)
|
||||
|
||||
@ -98,13 +99,13 @@ func (fd *FourByteSignaturesDownloader) getPageWithRetry(url string) (*signature
|
||||
return nil, errors.New("Too many retries to 4byte signatures")
|
||||
}
|
||||
|
||||
func parseSignatureFromText(t string) *db.FourByteSignature {
|
||||
func parseSignatureFromText(t string) *bchain.FourByteSignature {
|
||||
s := strings.Index(t, "(")
|
||||
e := strings.LastIndex(t, ")")
|
||||
if s < 0 || e < 0 {
|
||||
return nil
|
||||
}
|
||||
var signature db.FourByteSignature
|
||||
var signature bchain.FourByteSignature
|
||||
signature.Name = t[:s]
|
||||
params := t[s+1 : e]
|
||||
if len(params) > 0 {
|
||||
|
||||
@ -4,26 +4,26 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/trezor/blockbook/db"
|
||||
"github.com/trezor/blockbook/bchain"
|
||||
)
|
||||
|
||||
func Test_parseSignatureFromText(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
signature string
|
||||
want db.FourByteSignature
|
||||
want bchain.FourByteSignature
|
||||
}{
|
||||
{
|
||||
name: "_gonsPerFragment",
|
||||
signature: "_gonsPerFragment()",
|
||||
want: db.FourByteSignature{
|
||||
want: bchain.FourByteSignature{
|
||||
Name: "_gonsPerFragment",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vestingDeposits",
|
||||
signature: "vestingDeposits(address)",
|
||||
want: db.FourByteSignature{
|
||||
want: bchain.FourByteSignature{
|
||||
Name: "vestingDeposits",
|
||||
Parameters: []string{"address"},
|
||||
},
|
||||
@ -31,7 +31,7 @@ func Test_parseSignatureFromText(t *testing.T) {
|
||||
{
|
||||
name: "batchTransferTokenB",
|
||||
signature: "batchTransferTokenB(address[],uint256)",
|
||||
want: db.FourByteSignature{
|
||||
want: bchain.FourByteSignature{
|
||||
Name: "batchTransferTokenB",
|
||||
Parameters: []string{"address[]", "uint256"},
|
||||
},
|
||||
@ -39,7 +39,7 @@ func Test_parseSignatureFromText(t *testing.T) {
|
||||
{
|
||||
name: "transmitAndSellTokenForEth",
|
||||
signature: "transmitAndSellTokenForEth(address,uint256,uint256,uint256,address,(uint8,bytes32,bytes32),bytes)",
|
||||
want: db.FourByteSignature{
|
||||
want: bchain.FourByteSignature{
|
||||
Name: "transmitAndSellTokenForEth",
|
||||
Parameters: []string{"address", "uint256", "uint256", "uint256", "address", "(uint8,bytes32,bytes32)", "bytes"},
|
||||
},
|
||||
|
||||
@ -457,6 +457,8 @@ func (s *PublicServer) parseTemplates() []*template.Template {
|
||||
"isOwnAddress": isOwnAddress,
|
||||
"toJSON": toJSON,
|
||||
"tokenTransfersCount": tokenTransfersCount,
|
||||
"tokenCount": tokenCount,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
}
|
||||
var createTemplate func(filenames ...string) *template.Template
|
||||
if s.debug {
|
||||
@ -559,7 +561,7 @@ func isOwnAddress(td *TemplateData, a string) bool {
|
||||
return a == td.AddrStr
|
||||
}
|
||||
|
||||
// called from template, returns count of token transfers of given type
|
||||
// called from template, returns count of token transfers of given type in a tx
|
||||
func tokenTransfersCount(tx *api.Tx, t api.TokenType) int {
|
||||
count := 0
|
||||
for i := range tx.TokenTransfers {
|
||||
@ -570,6 +572,17 @@ func tokenTransfersCount(tx *api.Tx, t api.TokenType) int {
|
||||
return count
|
||||
}
|
||||
|
||||
// called from template, returns count of tokens in array of given type
|
||||
func tokenCount(tokens []api.Token, t api.TokenType) int {
|
||||
count := 0
|
||||
for i := range tokens {
|
||||
if tokens[i].Type == t {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||
var tx *api.Tx
|
||||
var err error
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -74,10 +74,15 @@ func setupRocksDB(parser bchain.BlockChainParser, chain bchain.BlockChain, t *te
|
||||
if err := d.ConnectBlock(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := InitTestFiatRates(d); err != nil {
|
||||
if err := initTestFiatRates(d); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
is.FinishedSync(block2.Height)
|
||||
if parser.GetChainType() == bchain.ChainEthereumType {
|
||||
if err := initEthereumTypeDB(d); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return d, is, tmp
|
||||
}
|
||||
|
||||
@ -168,8 +173,8 @@ func insertFiatRate(date string, rates map[string]float64, d *db.RocksDB) error
|
||||
return d.FiatRatesStoreTicker(ticker)
|
||||
}
|
||||
|
||||
// InitTestFiatRates initializes test data for /api/v2/tickers endpoint
|
||||
func InitTestFiatRates(d *db.RocksDB) error {
|
||||
// initTestFiatRates initializes test data for /api/v2/tickers endpoint
|
||||
func initTestFiatRates(d *db.RocksDB) error {
|
||||
if err := insertFiatRate("20180320020000", map[string]float64{
|
||||
"usd": 2000.0,
|
||||
"eur": 1300.0,
|
||||
@ -235,7 +240,7 @@ func performHttpTests(tests []httpTests, t *testing.T, ts *httptest.Server) {
|
||||
b := string(bb)
|
||||
for _, c := range tt.body {
|
||||
if !strings.Contains(b, c) {
|
||||
t.Errorf("got %v, want to contain %v", b, c)
|
||||
t.Errorf("got\n%v\nwant to contain %v", b, c)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
<td>Nonce</td>
|
||||
<td class="data">{{$addr.Nonce}}</td>
|
||||
</tr>
|
||||
{{- if $addr.Tokens -}}
|
||||
{{if tokenCount $addr.Tokens "ERC20"}}
|
||||
<tr>
|
||||
<td>ERC20 Tokens</td>
|
||||
<td style="padding: 0;">
|
||||
@ -41,13 +41,75 @@
|
||||
<th>Tokens</th>
|
||||
<th style="width: 15%;">Transfers</th>
|
||||
</tr>
|
||||
{{- range $t := $addr.Tokens -}}
|
||||
{{range $t := $addr.Tokens}}
|
||||
{{if eq $t.Type "ERC20"}}
|
||||
<tr>
|
||||
<td class="data ellipsis">{{if $t.Contract}}<a href="/address/{{$t.Contract}}">{{$t.Name}}</a>{{else}}{{$t.Name}}{{end}}</td>
|
||||
<td class="data">{{formatAmountWithDecimals $t.BalanceSat $t.Decimals}} {{$t.Symbol}}</td>
|
||||
<td class="data">{{$t.Transfers}}</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
{{if tokenCount $addr.Tokens "ERC721"}}
|
||||
<tr>
|
||||
<td>ERC721 Tokens</td>
|
||||
<td style="padding: 0;">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Contract</th>
|
||||
<th>Tokens</th>
|
||||
<th style="width: 15%;">Transfers</th>
|
||||
</tr>
|
||||
{{range $t := $addr.Tokens}}
|
||||
{{if eq $t.Type "ERC721"}}
|
||||
<tr>
|
||||
<td class="data ellipsis">{{if $t.Contract}}<a href="/address/{{$t.Contract}}">{{$t.Name}}</a>{{else}}{{$t.Name}}{{end}}</td>
|
||||
<td class="data">
|
||||
{{range $i, $iv := $t.Ids}}
|
||||
{{if $i}}, {{end}}
|
||||
{{formatAmountWithDecimals $iv 0}}
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="data">{{$t.Transfers}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
{{if tokenCount $addr.Tokens "ERC1155"}}
|
||||
<tr>
|
||||
<td>ERC1155 Tokens</td>
|
||||
<td style="padding: 0;">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Contract</th>
|
||||
<th>Tokens</th>
|
||||
<th style="width: 15%;">Transfers</th>
|
||||
</tr>
|
||||
{{range $t := $addr.Tokens}}
|
||||
{{if eq $t.Type "ERC1155"}}
|
||||
<tr>
|
||||
<td class="data ellipsis">{{if $t.Contract}}<a href="/address/{{$t.Contract}}">{{$t.Name}}</a>{{else}}{{$t.Name}}{{end}}</td>
|
||||
<td class="data">
|
||||
{{range $i, $iv := $t.IdValues}}
|
||||
{{if $i}}, {{end}}
|
||||
{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$t.Symbol}}
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="data">{{$t.Transfers}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
@ -84,6 +84,46 @@
|
||||
<div class="data-div">
|
||||
{{template "txdetail" .}}
|
||||
</div>
|
||||
{{if eq .ChainType 1}}
|
||||
{{if $tx.EthereumSpecific.ParsedData}}
|
||||
{{if $tx.EthereumSpecific.ParsedData.Function }}
|
||||
<div class="data-div">
|
||||
<h5>Input Data</h5>
|
||||
<div class="row">
|
||||
{{if $tx.EthereumSpecific.ParsedData.Name}}<div class="col-6">{{$tx.EthereumSpecific.ParsedData.Name}}</div>{{end}}{{if $tx.EthereumSpecific.ParsedData.MethodId}}<div class="col-6">Method ID: {{$tx.EthereumSpecific.ParsedData.MethodId}}</div>{{end}}
|
||||
{{if $tx.EthereumSpecific.ParsedData.Function}}<div class="col-12">Function: {{$tx.EthereumSpecific.ParsedData.Function}}</div>{{end}}
|
||||
{{if $tx.EthereumSpecific.ParsedData.Params}}
|
||||
<div class="col-12">
|
||||
<table class="table data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%;">#</th>
|
||||
<th>Type</th>
|
||||
<th>Data</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $i,$p := $tx.EthereumSpecific.ParsedData.Params}}
|
||||
<tr>
|
||||
<td>{{$i}}</td>
|
||||
<td>{{$p.Type}}</td>
|
||||
<td>
|
||||
{{range $j,$v := $p.Values}}
|
||||
{{if $j}}<br>{{end}}
|
||||
{{if hasPrefix $p.Type "address"}}<a href="/address/{{$v}}">{{$v}}</a>{{else}}{{$v}}{{end}}
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
<div class="data-div">
|
||||
<h5>Raw Transaction</h5>
|
||||
<div class="alert alert-data" style="word-wrap: break-word; font-size: smaller;">
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
{{if eq $tx.EthereumSpecific.Status 1}}<span class="text-success"> ✔</span>{{end}}{{if eq $tx.EthereumSpecific.Status 0}}<span class="text-danger"> ✘</span>{{end}}
|
||||
</div>
|
||||
{{- if $tx.Blocktime}}<div class="col-xs-5 col-md-4 text-muted text-right">{{if $tx.Confirmations}}mined{{else}}first seen{{end}} {{formatUnixTime $tx.Blocktime}}</div>{{end -}}
|
||||
{{if $tx.EthereumSpecific.ParsedData}}
|
||||
{{if $tx.EthereumSpecific.ParsedData.Name}}<div class="col-6">{{$tx.EthereumSpecific.ParsedData.Name}}</div>{{end}}{{if $tx.EthereumSpecific.ParsedData.MethodId}}<div class="col-6">Method ID: {{$tx.EthereumSpecific.ParsedData.MethodId}}</div>{{end}}
|
||||
{{end}}
|
||||
{{if $tx.EthereumSpecific.Error}}<div class="col-12">Error: {{$tx.EthereumSpecific.Error}}</div>{{end}}
|
||||
</div>
|
||||
<div class="row line-mid">
|
||||
@ -265,7 +268,7 @@
|
||||
</div>
|
||||
<div class="col-md-3 text-right" style="padding: .4rem 0;">
|
||||
{{- range $iv := $tt.Values -}}
|
||||
{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value $tt.Decimals}} {{$tt.Symbol}}
|
||||
{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$tt.Symbol}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user