Notify on mempool erc20 transfer transaction

ethereum
Martin Boehm 2018-12-19 10:06:25 +01:00
parent 2e9f87e39d
commit bab500d3f8
14 changed files with 71 additions and 51 deletions

View File

@ -218,7 +218,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
}
pValInSat = &valInSat
} else if w.chainType == bchain.ChainEthereumType {
ets, err := eth.GetErc20FromTx(bchainTx)
ets, err := w.chainParser.EthereumTypeGetErc20FromTx(bchainTx)
if err != nil {
glog.Errorf("GetErc20FromTx error %v, %v", err, bchainTx)
}

View File

@ -255,3 +255,8 @@ func (p *BaseParser) UnpackTx(buf []byte) (*Tx, uint32, error) {
}
return &tx, pt.Height, nil
}
// EthereumTypeGetErc20FromTx is unsupported
func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) {
return nil, errors.New("Not supported")
}

View File

@ -37,14 +37,6 @@ const erc20SymbolSignature = "0x95d89b41"
const erc20DecimalsSignature = "0x313ce567"
const erc20BalanceOf = "0x70a08231"
// Erc20Transfer contains a single ERC20 token transfer
type Erc20Transfer struct {
Contract string
From string
To string
Tokens big.Int
}
var cachedContracts = make(map[string]*bchain.Erc20Contract)
var cachedContractsMux sync.Mutex
@ -63,8 +55,8 @@ func addressFromPaddedHex(s string) (string, error) {
return a.String(), nil
}
func erc20GetTransfersFromLog(logs []*rpcLog) ([]Erc20Transfer, error) {
var r []Erc20Transfer
func erc20GetTransfersFromLog(logs []*rpcLog) ([]bchain.Erc20Transfer, error) {
var r []bchain.Erc20Transfer
for _, l := range logs {
if len(l.Topics) == 3 && l.Topics[0] == erc20TransferEventSignature {
var t big.Int
@ -80,7 +72,7 @@ func erc20GetTransfersFromLog(logs []*rpcLog) ([]Erc20Transfer, error) {
if err != nil {
return nil, err
}
r = append(r, Erc20Transfer{
r = append(r, bchain.Erc20Transfer{
Contract: strings.ToLower(l.Address),
From: strings.ToLower(from),
To: strings.ToLower(to),
@ -91,8 +83,8 @@ func erc20GetTransfersFromLog(logs []*rpcLog) ([]Erc20Transfer, error) {
return r, nil
}
func erc20GetTransfersFromTx(tx *rpcTransaction) ([]Erc20Transfer, error) {
var r []Erc20Transfer
func erc20GetTransfersFromTx(tx *rpcTransaction) ([]bchain.Erc20Transfer, error) {
var r []bchain.Erc20Transfer
if len(tx.Payload) == 128+len(erc20TransferMethodSignature) && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) {
to, err := addressFromPaddedHex(tx.Payload[len(erc20TransferMethodSignature) : 64+len(erc20TransferMethodSignature)])
if err != nil {
@ -103,7 +95,7 @@ func erc20GetTransfersFromTx(tx *rpcTransaction) ([]Erc20Transfer, error) {
if !ok {
return nil, errors.New("Data is not a number")
}
r = append(r, Erc20Transfer{
r = append(r, bchain.Erc20Transfer{
Contract: strings.ToLower(tx.To),
From: strings.ToLower(tx.From),
To: strings.ToLower(to),

View File

@ -3,6 +3,7 @@
package eth
import (
"blockbook/bchain"
"blockbook/tests/dbtestdata"
fmt "fmt"
"math/big"
@ -146,17 +147,17 @@ func TestErc20_erc20GetTransfersFromTx(t *testing.T) {
tests := []struct {
name string
args *rpcTransaction
want []Erc20Transfer
want []bchain.Erc20Transfer
}{
{
name: "0",
args: (b.Txs[0].CoinSpecificData.(completeTransaction)).Tx,
want: []Erc20Transfer{},
want: []bchain.Erc20Transfer{},
},
{
name: "1",
args: (b.Txs[1].CoinSpecificData.(completeTransaction)).Tx,
want: []Erc20Transfer{
want: []bchain.Erc20Transfer{
{
Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
From: "0x20cd153de35d469ba46127a0c8f18626b59a256a",

View File

@ -398,9 +398,9 @@ func GetHeightFromTx(tx *bchain.Tx) (uint32, error) {
return uint32(n), nil
}
// GetErc20FromTx returns Erc20 data from bchain.Tx
func GetErc20FromTx(tx *bchain.Tx) ([]Erc20Transfer, error) {
var r []Erc20Transfer
// EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx
func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) {
var r []bchain.Erc20Transfer
var err error
csd, ok := tx.CoinSpecificData.(completeTransaction)
if ok {

View File

@ -122,7 +122,7 @@ func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan Outpoint, ch
io = append(io, addrIndex{string(addrDesc), int32(output.N)})
}
if m.onNewTxAddr != nil {
m.onNewTxAddr(tx, addrDesc, true)
m.onNewTxAddr(tx, addrDesc)
}
}
dispatched := 0

View File

@ -44,6 +44,18 @@ func (m *MempoolEthereumType) updateMappings(newTxToInputOutput map[string][]add
m.addrDescToTx = newAddrDescToTx
}
func appendAddress(io []addrIndex, i int32, a string, parser BlockChainParser) []addrIndex {
if len(a) > 0 {
addrDesc, err := parser.GetAddrDescFromAddress(a)
if err != nil {
glog.Error("error in input addrDesc in ", a, ": ", err)
return io
}
io = append(io, addrIndex{string(addrDesc), i})
}
return io
}
// Resync gets mempool transactions and maps outputs to transactions.
// Resync is not reentrant, it should be called from a single thread.
// Read operations (GetTransactions) are safe.
@ -78,22 +90,27 @@ func (m *MempoolEthereumType) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) {
if len(addrDesc) > 0 {
io = append(io, addrIndex{string(addrDesc), int32(output.N)})
}
if onNewTxAddr != nil {
onNewTxAddr(tx, addrDesc, true)
}
}
for _, input := range tx.Vin {
for i, a := range input.Addresses {
if len(a) > 0 {
addrDesc, err := parser.GetAddrDescFromAddress(a)
if err != nil {
glog.Error("error in input addrDesc in ", txid, " ", a, ": ", err)
continue
}
io = append(io, addrIndex{string(addrDesc), int32(^i)})
if onNewTxAddr != nil {
onNewTxAddr(tx, addrDesc, false)
}
appendAddress(io, ^int32(i), a, parser)
}
}
t, err := parser.EthereumTypeGetErc20FromTx(tx)
if err != nil {
glog.Error("GetErc20FromTx for tx ", txid, ", ", err)
} else {
for i := range t {
io = appendAddress(io, ^int32(i+1), t[i].From, parser)
io = appendAddress(io, int32(i+1), t[i].To, parser)
}
}
if onNewTxAddr != nil {
sent := make(map[string]struct{})
for _, si := range io {
if _, found := sent[si.addrDesc]; !found {
onNewTxAddr(tx, AddressDescriptor(si.addrDesc))
sent[si.addrDesc] = struct{}{}
}
}
}

View File

@ -175,11 +175,19 @@ type Erc20Contract struct {
Decimals int `json:"decimals"`
}
// Erc20Transfer contains a single ERC20 token transfer
type Erc20Transfer struct {
Contract string
From string
To string
Tokens big.Int
}
// OnNewBlockFunc is used to send notification about a new block
type OnNewBlockFunc func(hash string, height uint32)
// OnNewTxAddrFunc is used to send notification about a new transaction/address
type OnNewTxAddrFunc func(tx *Tx, desc AddressDescriptor, isOutput bool)
type OnNewTxAddrFunc func(tx *Tx, desc AddressDescriptor)
// BlockChain defines common interface to block chain daemon
type BlockChain interface {
@ -252,4 +260,6 @@ type BlockChainParser interface {
PackBlockHash(hash string) ([]byte, error)
UnpackBlockHash(buf []byte) (string, error)
ParseBlock(b []byte) (*Block, error)
// EthereumType specific
EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error)
}

View File

@ -498,9 +498,9 @@ func storeInternalStateLoop() {
glog.Info("storeInternalStateLoop stopped")
}
func onNewTxAddr(tx *bchain.Tx, desc bchain.AddressDescriptor, isOutput bool) {
func onNewTxAddr(tx *bchain.Tx, desc bchain.AddressDescriptor) {
for _, c := range callbacksOnNewTxAddr {
c(tx, desc, isOutput)
c(tx, desc)
}
}

View File

@ -174,7 +174,7 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ma
blockTx.from = addrDesc
}
// store erc20 transfers
erc20, err := eth.GetErc20FromTx(&tx)
erc20, err := d.chainParser.EthereumTypeGetErc20FromTx(&tx)
if err != nil {
glog.Warningf("rocksdb: GetErc20FromTx %v - height %d, tx %v", err, block.Height, tx.Txid)
}

View File

@ -201,9 +201,9 @@ func (s *PublicServer) OnNewBlock(hash string, height uint32) {
}
// OnNewTxAddr notifies users subscribed to bitcoind/addresstxid about new block
func (s *PublicServer) OnNewTxAddr(tx *bchain.Tx, desc bchain.AddressDescriptor, isOutput bool) {
s.socketio.OnNewTxAddr(tx.Txid, desc, isOutput)
s.websocket.OnNewTxAddr(tx, desc, isOutput)
func (s *PublicServer) OnNewTxAddr(tx *bchain.Tx, desc bchain.AddressDescriptor) {
s.socketio.OnNewTxAddr(tx.Txid, desc)
s.websocket.OnNewTxAddr(tx, desc)
}
func (s *PublicServer) txRedirect(w http.ResponseWriter, r *http.Request) {

View File

@ -722,15 +722,12 @@ func (s *SocketIoServer) OnNewBlockHash(hash string) {
}
// OnNewTxAddr notifies users subscribed to bitcoind/addresstxid about new block
func (s *SocketIoServer) OnNewTxAddr(txid string, desc bchain.AddressDescriptor, isOutput bool) {
func (s *SocketIoServer) OnNewTxAddr(txid string, desc bchain.AddressDescriptor) {
addr, searchable, err := s.chainParser.GetAddressesFromAddrDesc(desc)
if err != nil {
glog.Error("GetAddressesFromAddrDesc error ", err, " for descriptor ", desc)
} else if searchable && len(addr) == 1 {
data := map[string]interface{}{"address": addr[0], "txid": txid}
if !isOutput {
data["input"] = true
}
c := s.server.BroadcastTo("bitcoind/addresstxid-"+string(desc), "bitcoind/addresstxid", data)
if c > 0 {
glog.Info("broadcasting new txid ", txid, " for addr ", addr[0], " to ", c, " channels")

View File

@ -531,7 +531,7 @@ func (s *WebsocketServer) OnNewBlock(hash string, height uint32) {
}
// OnNewTxAddr is a callback that broadcasts info about a tx affecting subscribed address
func (s *WebsocketServer) OnNewTxAddr(tx *bchain.Tx, addrDesc bchain.AddressDescriptor, isOutput bool) {
func (s *WebsocketServer) OnNewTxAddr(tx *bchain.Tx, addrDesc bchain.AddressDescriptor) {
// check if there is any subscription but release the lock immediately, GetTransactionFromBchainTx may take some time
s.addressSubscriptionsLock.Lock()
as, ok := s.addressSubscriptions[string(addrDesc)]
@ -550,11 +550,9 @@ func (s *WebsocketServer) OnNewTxAddr(tx *bchain.Tx, addrDesc bchain.AddressDesc
}
data := struct {
Address string `json:"address"`
Input bool `json:"input"`
Tx *api.Tx `json:"tx"`
}{
Address: addr[0],
Input: !isOutput,
Tx: atx,
}
// get the list of subscriptions again, this time keep the lock

View File

@ -257,7 +257,7 @@
</div>
<div class="col-8">
<div class="row" style="margin: 0;">
<input type="text" style="width: 79%" class="form-control" id="getAccountInfoDescriptor" value="0x65513ecd11fd3a5b1fefdcc6a500b025008405a2">
<input type="text" style="width: 79%" class="form-control" id="getAccountInfoDescriptor" value="0xba98d6a5ac827632e3457de7512d211e4ff7e8bd">
<select id="getAccountInfoDetails" style="width: 20%; margin-left: 5px;">
<option value="basic">Basic</option>
<option value="balance">Balance</option>
@ -328,7 +328,7 @@
<input class="btn btn-secondary" type="button" value="subscribe address" onclick="subscribeAddresses()">
</div>
<div class="col-8">
<input type="text" class="form-control" id="subscribeAddressesName" value="0x65513ecd11fd3a5b1fefdcc6a500b025008405a2,0xbdfeff9a1f4a1bdf483d680046344316019c58cf">
<input type="text" class="form-control" id="subscribeAddressesName" value="0xba98d6a5ac827632e3457de7512d211e4ff7e8bd,0x73d0385f4d8e00c5e6504c6030f47bf6212736a8">
</div>
<div class="col">
<span id="subscribeAddressesIds"></span>