Notify on mempool erc20 transfer transaction
parent
2e9f87e39d
commit
bab500d3f8
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue