diff --git a/api/types.go b/api/types.go index d43ce8d6..796573b9 100644 --- a/api/types.go +++ b/api/types.go @@ -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"` diff --git a/api/worker.go b/api/worker.go index 75b3770e..ecabcfde 100644 --- a/api/worker.go +++ b/api/worker.go @@ -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), diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index c3699d4b..680ae27a 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -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) } diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index fbef4dd5..b7b8f143 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -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) } diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index ad079e50..1f6e5140 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -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) } diff --git a/bchain/mempool_bitcoin_type.go b/bchain/mempool_bitcoin_type.go index 6cd30a24..c4eab212 100644 --- a/bchain/mempool_bitcoin_type.go +++ b/bchain/mempool_bitcoin_type.go @@ -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}) } } } diff --git a/bchain/mempool_ethereum_type.go b/bchain/mempool_ethereum_type.go index 660c020d..c8ef39ab 100644 --- a/bchain/mempool_ethereum_type.go +++ b/bchain/mempool_ethereum_type.go @@ -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) diff --git a/bchain/types.go b/bchain/types.go index 3a86379b..a90c939b 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -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 diff --git a/blockbook.go b/blockbook.go index 3ff1ac56..f2ff9f9b 100644 --- a/blockbook.go +++ b/blockbook.go @@ -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 } diff --git a/db/rocksdb.go b/db/rocksdb.go index 4577cdf0..555040f2 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -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) diff --git a/db/rocksdb_test.go b/db/rocksdb_test.go index 64eff3b7..8367c483 100644 --- a/db/rocksdb_test.go +++ b/db/rocksdb_test.go @@ -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 } diff --git a/server/public.go b/server/public.go index 8294499c..ea580fbf 100644 --- a/server/public.go +++ b/server/public.go @@ -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 } diff --git a/server/socketio.go b/server/socketio.go index 7a12a0d7..131f31e1 100644 --- a/server/socketio.go +++ b/server/socketio.go @@ -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) diff --git a/static/templates/address.html b/static/templates/address.html index 30c50855..f4e2600b 100644 --- a/static/templates/address.html +++ b/static/templates/address.html @@ -28,7 +28,7 @@ Contract Tokens - No. Txs + No. Txs {{- range $et := $addr.Erc20Tokens -}} @@ -88,10 +88,23 @@ -{{- end}}{{if $addr.Transactions -}} +{{- end}}{{if or $addr.Transactions $addr.Filter -}}

Transactions

- +
+ + +
{{- range $tx := $addr.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data}}{{end -}} diff --git a/static/templates/paging.html b/static/templates/paging.html index fa9e7245..c8260779 100644 --- a/static/templates/paging.html +++ b/static/templates/paging.html @@ -1,11 +1,11 @@ {{- define "paging"}}{{$data := . -}}{{if $data.PagingRange -}} {{- end -}}{{- end -}} \ No newline at end of file diff --git a/tests/dbtestdata/fakechain.go b/tests/dbtestdata/fakechain.go index e0643195..ffc0b2ce 100644 --- a/tests/dbtestdata/fakechain.go +++ b/tests/dbtestdata/fakechain.go @@ -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) {