Filter address transactions by input/output or token

ethereum
Martin Boehm 2018-12-04 11:54:15 +01:00
parent a08f568353
commit 9a04c862d6
16 changed files with 169 additions and 118 deletions

View File

@ -86,11 +86,12 @@ type Vout struct {
// Erc20Token contains info about ERC20 token held by an address
type Erc20Token struct {
Contract string `json:"contract"`
Txs int `json:"txs"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Balance string `json:"balance"`
Contract string `json:"contract"`
Txs int `json:"txs"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Balance string `json:"balance"`
ContractIndex string `json:"-"`
}
// Erc20Transfer contains info about ERC20 transfer done in a transaction
@ -136,6 +137,15 @@ type Paging struct {
ItemsOnPage int `json:"itemsOnPage"`
}
// AddressFilterNone disables filtering of transactions
const AddressFilterNone = -1
// AddressFilterInputs specifies that only txs where the address is as input are returned
const AddressFilterInputs = -2
// AddressFilterOutputs specifies that only txs where the address is as output are returned
const AddressFilterOutputs = -3
// Address holds information about address and its transactions
type Address struct {
Paging
@ -150,12 +160,13 @@ type Address struct {
Txids []string `json:"transactions,omitempty"`
Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"`
Erc20Tokens []Erc20Token `json:"erc20tokens,omitempty"`
Filter string `json:"-"`
}
// AddressUtxo holds information about address and its transactions
type AddressUtxo struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Vout int32 `json:"vout"`
Amount string `json:"amount"`
AmountSat big.Int `json:"satoshis"`
Height int `json:"height,omitempty"`

View File

@ -51,7 +51,7 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
// there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index int32, isOutput bool) error {
if isOutput == false {
tsp, err := w.db.GetTxAddresses(t)
if err != nil {
@ -290,24 +290,37 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool
return r, nil
}
func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool) ([]string, error) {
func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, filter int) ([]string, error) {
var err error
txids := make([]string, 0, 4)
if !mempool {
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
addFilteredTxid := func(txid string, vout int32, isOutput bool) error {
if filter == AddressFilterNone ||
(filter == AddressFilterInputs && !isOutput) ||
(filter == AddressFilterOutputs && isOutput) ||
(vout == int32(filter)) {
txids = append(txids, txid)
// glog.Info(txid, " ", vout, " ", isOutput)
return nil
})
}
return nil
}
if mempool {
o, err := w.chain.GetMempoolTransactionsForAddrDesc(addrDesc)
if err != nil {
return nil, err
}
for _, m := range o {
vout := m.Vout
isOutput := true
if vout < 0 {
isOutput = false
vout = ^vout
}
addFilteredTxid(m.Txid, vout, isOutput)
}
} else {
m, err := w.chain.GetMempoolTransactionsForAddrDesc(addrDesc)
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), addFilteredTxid)
if err != nil {
return nil, err
}
txids = append(txids, m...)
}
return txids, nil
}
@ -463,11 +476,12 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract)
}
erc20t[i] = Erc20Token{
Balance: bchain.AmountToDecimalString(b, ci.Decimals),
Contract: ci.Contract,
Name: ci.Name,
Symbol: ci.Symbol,
Txs: int(c.Txs),
Balance: bchain.AmountToDecimalString(b, ci.Decimals),
Contract: ci.Contract,
Name: ci.Name,
Symbol: ci.Symbol,
Txs: int(c.Txs),
ContractIndex: strconv.Itoa(i + 1),
}
}
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
@ -480,7 +494,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
}
// GetAddress computes address value and gets transactions for given address
func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetAddressOption) (*Address, error) {
func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetAddressOption, filter int) (*Address, error) {
start := time.Now()
page--
if page < 0 {
@ -519,7 +533,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
if len(addresses) == 1 {
address = addresses[0]
}
txc, err := w.getAddressTxids(addrDesc, false)
txc, err := w.getAddressTxids(addrDesc, false, filter)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
}
@ -530,7 +544,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
ba = &db.AddrBalance{}
page = 0
}
txm, err = w.getAddressTxids(addrDesc, true)
txm, err = w.getAddressTxids(addrDesc, true, filter)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
}
@ -538,7 +552,11 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
// check if the address exist
if len(txc)+len(txm) == 0 || option == ExistOnly {
return &Address{
AddrStr: address,
AddrStr: address,
Balance: w.chainParser.AmountToDecimalString(&ba.BalanceSat),
TotalReceived: w.chainParser.AmountToDecimalString(ba.ReceivedSat()),
TotalSent: w.chainParser.AmountToDecimalString(&ba.SentSat),
TxApperances: int(ba.Txs),
}, nil
}
bestheight, _, err := w.db.GetBestBlock()
@ -574,7 +592,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
}
}
}
if len(txc) != int(ba.Txs) && w.chainType == bchain.ChainBitcoinType {
if len(txc) != int(ba.Txs) && w.chainType == bchain.ChainBitcoinType && filter == AddressFilterNone {
glog.Warning("DB inconsistency for address ", address, ": number of txs from column addresses ", len(txc), ", from addressBalance ", ba.Txs)
}
for i := from; i < to; i++ {
@ -620,7 +638,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
Balance: w.chainParser.AmountToDecimalString(&ba.BalanceSat),
TotalReceived: w.chainParser.AmountToDecimalString(ba.ReceivedSat()),
TotalSent: w.chainParser.AmountToDecimalString(&ba.SentSat),
TxApperances: len(txc),
TxApperances: int(ba.Txs),
UnconfirmedBalance: w.chainParser.AmountToDecimalString(&uBalSat),
UnconfirmedTxApperances: len(txm),
Transactions: txs,
@ -643,7 +661,7 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
r := make([]AddressUtxo, 0, 8)
if !onlyConfirmed {
// get utxo from mempool
txm, err := w.getAddressTxids(addrDesc, true)
txm, err := w.getAddressTxids(addrDesc, true, AddressFilterNone)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
}
@ -675,7 +693,7 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
if !e {
r = append(r, AddressUtxo{
Txid: bchainTx.Txid,
Vout: uint32(i),
Vout: int32(i),
AmountSat: vout.ValueSat,
Amount: w.chainParser.AmountToDecimalString(&vout.ValueSat),
})
@ -693,14 +711,10 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
var checksum big.Int
// ba can be nil if the address is only in mempool!
if ba != nil && ba.BalanceSat.Uint64() > 0 {
type outpoint struct {
txid string
vout uint32
}
outpoints := make([]outpoint, 0, 8)
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
outpoints := make([]bchain.Outpoint, 0, 8)
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout int32, isOutput bool) error {
if isOutput {
outpoints = append(outpoints, outpoint{txid, vout})
outpoints = append(outpoints, bchain.Outpoint{txid, vout})
}
return nil
})
@ -717,27 +731,27 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
bestheight := int(b)
for i := len(outpoints) - 1; i >= 0 && checksum.Int64() > 0; i-- {
o := outpoints[i]
if lastTxid != o.txid {
ta, err = w.db.GetTxAddresses(o.txid)
if lastTxid != o.Txid {
ta, err = w.db.GetTxAddresses(o.Txid)
if err != nil {
return nil, err
}
lastTxid = o.txid
lastTxid = o.Txid
}
if ta == nil {
glog.Warning("DB inconsistency: tx ", o.txid, ": not found in txAddresses")
glog.Warning("DB inconsistency: tx ", o.Txid, ": not found in txAddresses")
} else {
if len(ta.Outputs) <= int(o.vout) {
glog.Warning("DB inconsistency: txAddresses ", o.txid, " does not have enough outputs")
if len(ta.Outputs) <= int(o.Vout) {
glog.Warning("DB inconsistency: txAddresses ", o.Txid, " does not have enough outputs")
} else {
if !ta.Outputs[o.vout].Spent {
v := ta.Outputs[o.vout].ValueSat
if !ta.Outputs[o.Vout].Spent {
v := ta.Outputs[o.Vout].ValueSat
// report only outpoints that are not spent in mempool
_, e := spentInMempool[o.txid+strconv.Itoa(int(o.vout))]
_, e := spentInMempool[o.Txid+strconv.Itoa(int(o.Vout))]
if !e {
r = append(r, AddressUtxo{
Txid: o.txid,
Vout: o.vout,
Txid: o.Txid,
Vout: o.Vout,
AmountSat: v,
Amount: w.chainParser.AmountToDecimalString(&v),
Height: int(ta.Height),

View File

@ -221,12 +221,12 @@ func (c *blockChainWithMetrics) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc
return count, err
}
func (c *blockChainWithMetrics) GetMempoolTransactions(address string) (v []string, err error) {
func (c *blockChainWithMetrics) GetMempoolTransactions(address string) (v []bchain.Outpoint, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactions", s, err) }(time.Now())
return c.b.GetMempoolTransactions(address)
}
func (c *blockChainWithMetrics) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) (v []string, err error) {
func (c *blockChainWithMetrics) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) (v []bchain.Outpoint, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactionsForAddrDesc", s, err) }(time.Now())
return c.b.GetMempoolTransactionsForAddrDesc(addrDesc)
}

View File

@ -709,12 +709,12 @@ func (b *BitcoinRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, err
}
// GetMempoolTransactions returns slice of mempool transactions for given address
func (b *BitcoinRPC) GetMempoolTransactions(address string) ([]string, error) {
func (b *BitcoinRPC) GetMempoolTransactions(address string) ([]bchain.Outpoint, error) {
return b.Mempool.GetTransactions(address)
}
// GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
func (b *BitcoinRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, error) {
func (b *BitcoinRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]bchain.Outpoint, error) {
return b.Mempool.GetAddrDescTransactions(addrDesc)
}

View File

@ -673,12 +673,12 @@ func (b *EthereumRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, er
}
// GetMempoolTransactions returns slice of mempool transactions for given address
func (b *EthereumRPC) GetMempoolTransactions(address string) ([]string, error) {
func (b *EthereumRPC) GetMempoolTransactions(address string) ([]bchain.Outpoint, error) {
return b.Mempool.GetTransactions(address)
}
// GetMempoolTransactionsForAddrDesc returns slice of mempool transactions for given address descriptor
func (b *EthereumRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, error) {
func (b *EthereumRPC) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) ([]bchain.Outpoint, error) {
return b.Mempool.GetAddrDescTransactions(addrDesc)
}

View File

@ -7,17 +7,11 @@ import (
"github.com/golang/glog"
)
// addrIndex and outpoint are used also in non utxo mempool
type addrIndex struct {
addrDesc string
n int32
}
type outpoint struct {
txid string
vout int32
}
type txidio struct {
txid string
io []addrIndex
@ -28,7 +22,7 @@ type MempoolBitcoinType struct {
chain BlockChain
mux sync.Mutex
txToInputOutput map[string][]addrIndex
addrDescToTx map[string][]outpoint
addrDescToTx map[string][]Outpoint
chanTxid chan string
chanAddrIndex chan txidio
onNewTxAddr OnNewTxAddrFunc
@ -44,7 +38,7 @@ func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int) *Mempo
}
for i := 0; i < workers; i++ {
go func(i int) {
chanInput := make(chan outpoint, 1)
chanInput := make(chan Outpoint, 1)
chanResult := make(chan *addrIndex, 1)
for j := 0; j < subworkers; j++ {
go func(j int) {
@ -68,7 +62,7 @@ func NewMempoolBitcoinType(chain BlockChain, workers int, subworkers int) *Mempo
}
// GetTransactions returns slice of mempool transactions for given address
func (m *MempoolBitcoinType) GetTransactions(address string) ([]string, error) {
func (m *MempoolBitcoinType) GetTransactions(address string) ([]Outpoint, error) {
parser := m.chain.GetChainParser()
addrDesc, err := parser.GetAddrDescFromAddress(address)
if err != nil {
@ -78,44 +72,39 @@ func (m *MempoolBitcoinType) GetTransactions(address string) ([]string, error) {
}
// GetAddrDescTransactions returns slice of mempool transactions for given address descriptor
func (m *MempoolBitcoinType) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]string, error) {
func (m *MempoolBitcoinType) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]Outpoint, error) {
m.mux.Lock()
defer m.mux.Unlock()
outpoints := m.addrDescToTx[string(addrDesc)]
txs := make([]string, 0, len(outpoints))
for _, o := range outpoints {
txs = append(txs, o.txid)
}
return txs, nil
return append([]Outpoint(nil), m.addrDescToTx[string(addrDesc)]...), nil
}
func (m *MempoolBitcoinType) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]outpoint) {
func (m *MempoolBitcoinType) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]Outpoint) {
m.mux.Lock()
defer m.mux.Unlock()
m.txToInputOutput = newTxToInputOutput
m.addrDescToTx = newAddrDescToTx
}
func (m *MempoolBitcoinType) getInputAddress(input outpoint) *addrIndex {
itx, err := m.chain.GetTransactionForMempool(input.txid)
func (m *MempoolBitcoinType) getInputAddress(input Outpoint) *addrIndex {
itx, err := m.chain.GetTransactionForMempool(input.Txid)
if err != nil {
glog.Error("cannot get transaction ", input.txid, ": ", err)
glog.Error("cannot get transaction ", input.Txid, ": ", err)
return nil
}
if int(input.vout) >= len(itx.Vout) {
glog.Error("Vout len in transaction ", input.txid, " ", len(itx.Vout), " input.Vout=", input.vout)
if int(input.Vout) >= len(itx.Vout) {
glog.Error("Vout len in transaction ", input.Txid, " ", len(itx.Vout), " input.Vout=", input.Vout)
return nil
}
addrDesc, err := m.chain.GetChainParser().GetAddrDescFromVout(&itx.Vout[input.vout])
addrDesc, err := m.chain.GetChainParser().GetAddrDescFromVout(&itx.Vout[input.Vout])
if err != nil {
glog.Error("error in addrDesc in ", input.txid, " ", input.vout, ": ", err)
glog.Error("error in addrDesc in ", input.Txid, " ", input.Vout, ": ", err)
return nil
}
return &addrIndex{string(addrDesc), ^input.vout}
return &addrIndex{string(addrDesc), ^input.Vout}
}
func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan outpoint, chanResult chan *addrIndex) ([]addrIndex, bool) {
func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan Outpoint, chanResult chan *addrIndex) ([]addrIndex, bool) {
tx, err := m.chain.GetTransactionForMempool(txid)
if err != nil {
glog.Error("cannot get transaction ", txid, ": ", err)
@ -141,7 +130,7 @@ func (m *MempoolBitcoinType) getTxAddrs(txid string, chanInput chan outpoint, ch
if input.Coinbase != "" {
continue
}
o := outpoint{input.Txid, int32(input.Vout)}
o := Outpoint{input.Txid, int32(input.Vout)}
loop:
for {
select {
@ -181,13 +170,13 @@ func (m *MempoolBitcoinType) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) {
glog.V(2).Info("mempool: resync ", len(txs), " txs")
// allocate slightly larger capacity of the maps
newTxToInputOutput := make(map[string][]addrIndex, len(m.txToInputOutput)+5)
newAddrDescToTx := make(map[string][]outpoint, len(m.addrDescToTx)+5)
newAddrDescToTx := make(map[string][]Outpoint, len(m.addrDescToTx)+5)
dispatched := 0
onNewData := func(txid string, io []addrIndex) {
if len(io) > 0 {
newTxToInputOutput[txid] = io
for _, si := range io {
newAddrDescToTx[si.addrDesc] = append(newAddrDescToTx[si.addrDesc], outpoint{txid, si.n})
newAddrDescToTx[si.addrDesc] = append(newAddrDescToTx[si.addrDesc], Outpoint{txid, si.n})
}
}
}

View File

@ -12,7 +12,7 @@ type MempoolEthereumType struct {
chain BlockChain
mux sync.Mutex
txToInputOutput map[string][]addrIndex
addrDescToTx map[string][]outpoint
addrDescToTx map[string][]Outpoint
}
// NewMempoolEthereumType creates new mempool handler.
@ -21,7 +21,7 @@ func NewMempoolEthereumType(chain BlockChain) *MempoolEthereumType {
}
// GetTransactions returns slice of mempool transactions for given address
func (m *MempoolEthereumType) GetTransactions(address string) ([]string, error) {
func (m *MempoolEthereumType) GetTransactions(address string) ([]Outpoint, error) {
parser := m.chain.GetChainParser()
addrDesc, err := parser.GetAddrDescFromAddress(address)
if err != nil {
@ -31,18 +31,13 @@ func (m *MempoolEthereumType) GetTransactions(address string) ([]string, error)
}
// GetAddrDescTransactions returns slice of mempool transactions for given address descriptor
func (m *MempoolEthereumType) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]string, error) {
func (m *MempoolEthereumType) GetAddrDescTransactions(addrDesc AddressDescriptor) ([]Outpoint, error) {
m.mux.Lock()
defer m.mux.Unlock()
outpoints := m.addrDescToTx[string(addrDesc)]
txs := make([]string, 0, len(outpoints))
for _, o := range outpoints {
txs = append(txs, o.txid)
}
return txs, nil
return append([]Outpoint(nil), m.addrDescToTx[string(addrDesc)]...), nil
}
func (m *MempoolEthereumType) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]outpoint) {
func (m *MempoolEthereumType) updateMappings(newTxToInputOutput map[string][]addrIndex, newAddrDescToTx map[string][]Outpoint) {
m.mux.Lock()
defer m.mux.Unlock()
m.txToInputOutput = newTxToInputOutput
@ -62,7 +57,7 @@ func (m *MempoolEthereumType) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) {
parser := m.chain.GetChainParser()
// allocate slightly larger capacity of the maps
newTxToInputOutput := make(map[string][]addrIndex, len(m.txToInputOutput)+5)
newAddrDescToTx := make(map[string][]outpoint, len(m.addrDescToTx)+5)
newAddrDescToTx := make(map[string][]Outpoint, len(m.addrDescToTx)+5)
for _, txid := range txs {
io, exists := m.txToInputOutput[txid]
if !exists {
@ -105,7 +100,7 @@ func (m *MempoolEthereumType) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) {
}
newTxToInputOutput[txid] = io
for _, si := range io {
newAddrDescToTx[si.addrDesc] = append(newAddrDescToTx[si.addrDesc], outpoint{txid, si.n})
newAddrDescToTx[si.addrDesc] = append(newAddrDescToTx[si.addrDesc], Outpoint{txid, si.n})
}
}
m.updateMappings(newTxToInputOutput, newAddrDescToTx)

View File

@ -33,6 +33,12 @@ var (
ErrTxidMissing = errors.New("Txid missing")
)
// Outpoint is txid together with output (or input) index
type Outpoint struct {
Txid string
Vout int32
}
// ScriptSig contains data about input script
type ScriptSig struct {
// Asm string `json:"asm"`
@ -202,8 +208,8 @@ type BlockChain interface {
SendRawTransaction(tx string) (string, error)
// mempool
ResyncMempool(onNewTxAddr OnNewTxAddrFunc) (int, error)
GetMempoolTransactions(address string) ([]string, error)
GetMempoolTransactionsForAddrDesc(addrDesc AddressDescriptor) ([]string, error)
GetMempoolTransactions(address string) ([]Outpoint, error)
GetMempoolTransactionsForAddrDesc(addrDesc AddressDescriptor) ([]Outpoint, error)
GetMempoolEntry(txid string) (*MempoolEntry, error)
// parser
GetChainParser() BlockChainParser

View File

@ -545,7 +545,7 @@ func waitForSignalAndShutdown(internal *server.InternalServer, public *server.Pu
}
}
func printResult(txid string, vout uint32, isOutput bool) error {
func printResult(txid string, vout int32, isOutput bool) error {
glog.Info(txid, vout, isOutput)
return nil
}

View File

@ -208,7 +208,7 @@ func (e *StopIteration) Error() string {
// GetTransactions finds all input/output transactions for address
// Transaction are passed to callback function.
func (d *RocksDB) GetTransactions(address string, lower uint32, higher uint32, fn func(txid string, vout uint32, isOutput bool) error) (err error) {
func (d *RocksDB) GetTransactions(address string, lower uint32, higher uint32, fn func(txid string, vout int32, isOutput bool) error) (err error) {
if glog.V(1) {
glog.Infof("rocksdb: address get %s %d-%d ", address, lower, higher)
}
@ -221,7 +221,7 @@ func (d *RocksDB) GetTransactions(address string, lower uint32, higher uint32, f
// GetAddrDescTransactions finds all input/output transactions for address descriptor
// Transaction are passed to callback function.
func (d *RocksDB) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor, lower uint32, higher uint32, fn func(txid string, vout uint32, isOutput bool) error) (err error) {
func (d *RocksDB) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor, lower uint32, higher uint32, fn func(txid string, vout int32, isOutput bool) error) (err error) {
kstart := packAddressKey(addrDesc, lower)
kstop := packAddressKey(addrDesc, higher)
@ -242,13 +242,13 @@ func (d *RocksDB) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor, low
glog.Infof("rocksdb: output %s: %s", hex.EncodeToString(key), hex.EncodeToString(val))
}
for _, o := range outpoints {
var vout uint32
var vout int32
var isOutput bool
if o.index < 0 {
vout = uint32(^o.index)
vout = int32(^o.index)
isOutput = false
} else {
vout = uint32(o.index)
vout = int32(o.index)
isOutput = true
}
tx, err := d.chainParser.UnpackTxid(o.btxID)

View File

@ -371,13 +371,13 @@ func verifyAfterBitcoinTypeBlock2(t *testing.T, d *RocksDB) {
type txidVoutOutput struct {
txid string
vout uint32
vout int32
isOutput bool
}
func verifyGetTransactions(t *testing.T, d *RocksDB, addr string, low, high uint32, wantTxids []txidVoutOutput, wantErr error) {
gotTxids := make([]txidVoutOutput, 0)
addToTxids := func(txid string, vout uint32, isOutput bool) error {
addToTxids := func(txid string, vout int32, isOutput bool) error {
gotTxids = append(gotTxids, txidVoutOutput{txid, vout, isOutput})
return nil
}

View File

@ -349,6 +349,7 @@ type TemplateData struct {
PrevPage int
NextPage int
PagingRange []int
PageParams template.URL
TOSLink string
SendTxHex string
Status string
@ -442,6 +443,8 @@ func (s *PublicServer) explorerSpendingTx(w http.ResponseWriter, r *http.Request
func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
var address *api.Address
var filter string
var fn = api.AddressFilterNone
var err error
s.metrics.ExplorerViews.With(common.Labels{"action": "address"}).Inc()
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
@ -449,7 +452,21 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
if ec != nil {
page = 0
}
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, api.TxHistory)
filter = r.URL.Query().Get("filter")
if len(filter) > 0 {
if filter == "inputs" {
fn = api.AddressFilterInputs
} else if filter == "outputs" {
fn = api.AddressFilterOutputs
} else {
fn, ec = strconv.Atoi(filter)
if ec != nil || fn < 0 {
filter = ""
fn = api.AddressFilterNone
}
}
}
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, api.TxHistory, fn)
if err != nil {
return errorTpl, nil, err
}
@ -459,6 +476,10 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
data.Address = address
data.Page = address.Page
data.PagingRange, data.PrevPage, data.NextPage = getPagingRange(address.Page, address.TotalPages)
if filter != "" {
data.PageParams = template.URL("&filter=" + filter)
data.Address.Filter = filter
}
return addressTpl, data, nil
}
@ -533,7 +554,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
return noTpl, nil, nil
}
address, err = s.api.GetAddress(q, 0, 1, api.ExistOnly)
address, err = s.api.GetAddress(q, 0, 1, api.ExistOnly, api.AddressFilterNone)
if err == nil {
http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302)
return noTpl, nil, nil
@ -688,7 +709,7 @@ func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
if ec != nil {
page = 0
}
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, api.TxidHistory)
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, api.TxidHistory, api.AddressFilterNone)
}
return address, err
}

View File

@ -216,7 +216,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, opts *addrOpts) (res res
lower, higher := uint32(opts.End), uint32(opts.Start)
for _, address := range addr {
if !opts.QueryMempoolOnly {
err = s.db.GetTransactions(address, lower, higher, func(txid string, vout uint32, isOutput bool) error {
err = s.db.GetTransactions(address, lower, higher, func(txid string, vout int32, isOutput bool) error {
txids = append(txids, txid)
return nil
})
@ -224,11 +224,13 @@ func (s *SocketIoServer) getAddressTxids(addr []string, opts *addrOpts) (res res
return res, err
}
} else {
m, err := s.chain.GetMempoolTransactions(address)
o, err := s.chain.GetMempoolTransactions(address)
if err != nil {
return res, err
}
txids = append(txids, m...)
for _, m := range o {
txids = append(txids, m.Txid)
}
}
}
res.Result = api.UniqueTxidsInReverse(txids)

View File

@ -28,7 +28,7 @@
<tr>
<th>Contract</th>
<th>Tokens</th>
<th style="width: 15%;">No. Txs</th>
<th style="width: 15%;">No. Txs</th>
</tr>
{{- range $et := $addr.Erc20Tokens -}}
<tr>
@ -88,10 +88,23 @@
</tbody>
</table>
</div>
{{- end}}{{if $addr.Transactions -}}
{{- end}}{{if or $addr.Transactions $addr.Filter -}}
<div class="row h-container">
<h3 class="col-md-6 col-sm-12">Transactions</h3>
<nav class="col-md-6 col-sm-12">{{template "paging" $data}}</nav>
<div class="col-md-6 col-sm-12">
<select style="float: left;margin-top: 6px;" onchange="self.location='?filter='+options[selectedIndex].value">
<option>All</option>
<option {{if eq $addr.Filter "inputs" -}} selected{{end}} value="inputs">Inputs</option>
<option {{if eq $addr.Filter "outputs" -}} selected{{end}} value="outputs">Outputs</option>
{{- if $addr.Erc20Tokens -}}
<option {{if eq $addr.Filter "0" -}} selected{{end}} value="0">Non-contract</option>
{{- range $et := $addr.Erc20Tokens -}}
<option {{if eq $addr.Filter $et.ContractIndex -}} selected{{end}} value="{{$et.ContractIndex}}">{{$et.Name}}</option>
{{- end -}}
{{- end -}}
</select>
<nav>{{template "paging" $data}}</nav>
</div>
</div>
<div class="data-div">
{{- range $tx := $addr.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data}}{{end -}}

View File

@ -1,11 +1,11 @@
{{- define "paging"}}{{$data := . -}}{{if $data.PagingRange -}}
<ul class="pagination justify-content-end">
<li class="page-item"><a class="page-link" href="?page={{$data.PrevPage}}">&lt;</a></li>
<li class="page-item"><a class="page-link" href="?page={{$data.PrevPage}}{{$data.PageParams}}">&lt;</a></li>
{{- range $p := $data.PagingRange -}}
<li class="page-item{{if eq $data.Page $p}} active{{end}}">
{{- if $p}}<a class="page-link" href="?page={{$p}}">{{$p}}</a>
{{- if $p}}<a class="page-link" href="?page={{$p}}{{$data.PageParams}}">{{$p}}</a>
{{- else -}}<span class="page-text">...</span>{{- end -}}
</li>{{- end -}}
<li class="page-item"><a class="page-link" href="?page={{$data.NextPage}}">&gt;</a></li>
<li class="page-item"><a class="page-link" href="?page={{$data.NextPage}}{{$data.PageParams}}">&gt;</a></li>
</ul>
{{- end -}}{{- end -}}

View File

@ -182,12 +182,12 @@ func (c *fakeBlockChain) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (coun
return 0, errors.New("Not implemented")
}
func (c *fakeBlockChain) GetMempoolTransactions(address string) (v []string, err error) {
func (c *fakeBlockChain) GetMempoolTransactions(address string) (v []bchain.Outpoint, err error) {
return nil, errors.New("Not implemented")
}
func (c *fakeBlockChain) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) (v []string, err error) {
return []string{}, nil
func (c *fakeBlockChain) GetMempoolTransactionsForAddrDesc(addrDesc bchain.AddressDescriptor) (v []bchain.Outpoint, err error) {
return []bchain.Outpoint{}, nil
}
func (c *fakeBlockChain) GetMempoolEntry(txid string) (v *bchain.MempoolEntry, err error) {