Implement Bitcore socket.io method getAddressHistory

This commit is contained in:
Martin Boehm 2018-02-08 13:28:10 +01:00
parent d96af8e648
commit 90a9edda41
3 changed files with 256 additions and 3 deletions

View File

@ -45,6 +45,7 @@ func AddressToOutputScript(address string) ([]byte, error) {
}
type Tx struct {
Hex string `json:"hex"`
Txid string `json:"txid"`
Version int32 `json:"version"`
LockTime uint32 `json:"locktime"`

View File

@ -112,12 +112,19 @@ type reqRange struct {
var onMessageHandlers = map[string]func(*SocketIoServer, json.RawMessage) (interface{}, error){
"\"getAddressTxids\"": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) {
addr, rr, err := unmarshalGetAddressTxids(params)
addr, rr, err := unmarshalGetAddressRequest(params)
if err == nil {
rv, err = s.getAddressTxids(addr, &rr)
}
return
},
"\"getAddressHistory\"": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) {
addr, rr, err := unmarshalGetAddressRequest(params)
if err == nil {
rv, err = s.getAddressHistory(addr, &rr)
}
return
},
"\"getBlockHeader\"": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) {
height, hash, err := unmarshalGetBlockHeader(params)
if err == nil {
@ -156,7 +163,7 @@ func (s *SocketIoServer) onMessage(c *gosocketio.Channel, req map[string]json.Ra
return ""
}
func unmarshalGetAddressTxids(params []byte) (addr []string, rr reqRange, err error) {
func unmarshalGetAddressRequest(params []byte) (addr []string, rr reqRange, err error) {
var p []json.RawMessage
err = json.Unmarshal(params, &p)
if err != nil {
@ -211,6 +218,185 @@ func (s *SocketIoServer) getAddressTxids(addr []string, rr *reqRange) ([]string,
return txids, nil
}
type addressHistoryIndexes struct {
InputIndexes []int `json:"inputIndexes"`
OutputIndexes []int `json:"outputIndexes"`
}
type addressHistoryInputs struct {
PrevTxID string `json:"prevTxId"`
OutputIndex int `json:"outputIndex"`
Script string `json:"script"`
ScriptAsm string `json:"scriptAsm"`
Sequence int64 `json:"sequence"`
Address string `json:"address"`
Satoshis int64 `json:"satoshis"`
}
type addressHistoryOutputs struct {
Satoshis int64 `json:"satoshis"`
Script string `json:"script"`
ScriptAsm string `json:"scriptAsm"`
SpentTxID string `json:"spentTxId,omitempty"`
SpentIndex int `json:"spentIndex,omitempty"`
SpentHeight int `json:"spentHeight,omitempty"`
Address string `json:"address"`
}
type addressHistoryItem struct {
Addresses map[string]addressHistoryIndexes `json:"addresses"`
Satoshis int `json:"satoshis"`
Confirmations int `json:"confirmations"`
Tx struct {
Hex string `json:"hex"`
BlockHash string `json:"blockHash"`
Height int `json:"height"`
BlockTimestamp int64 `json:"blockTimestamp"`
Version int `json:"version"`
Hash string `json:"hash"`
Locktime int `json:"locktime"`
Size int `json:"size"`
Inputs []addressHistoryInputs `json:"inputs"`
InputSatoshis int64 `json:"inputSatoshis"`
Outputs []addressHistoryOutputs `json:"outputs"`
OutputSatoshis int64 `json:"outputSatoshis"`
FeeSatoshis int64 `json:"feeSatoshis"`
} `json:"tx"`
}
type resultGetAddressHistory struct {
Result struct {
TotalCount int `json:"totalCount"`
Items []addressHistoryItem `json:"items"`
} `json:"result"`
}
func uniqueTxids(txids []string) []string {
uniqueTxids := make([]string, 0, len(txids))
txidsMap := make(map[string]struct{})
for _, txid := range txids {
_, e := txidsMap[txid]
if !e {
uniqueTxids = append(uniqueTxids, txid)
txidsMap[txid] = struct{}{}
}
}
return uniqueTxids
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
func (s *SocketIoServer) getAddressHistory(addr []string, rr *reqRange) (res resultGetAddressHistory, err error) {
txids, err := s.getAddressTxids(addr, rr)
if err != nil {
return
}
bestheight, _, err := s.db.GetBestBlock()
if err != nil {
return
}
// todo - proper sorting of txids, probably by height desc
txids = uniqueTxids(txids)
res.Result.TotalCount = len(txids)
res.Result.Items = make([]addressHistoryItem, 0)
for i, txid := range txids {
if i >= rr.From && i < rr.To {
tx, err := s.chain.GetTransaction(txid)
if err != nil {
return res, err
}
ads := make(map[string]addressHistoryIndexes)
hi := make([]addressHistoryInputs, 0)
ho := make([]addressHistoryOutputs, 0)
for _, vin := range tx.Vin {
ai := addressHistoryInputs{
Script: vin.ScriptSig.Hex,
ScriptAsm: vin.ScriptSig.Asm,
Sequence: int64(vin.Sequence),
OutputIndex: int(vin.Vout),
}
stxid, _, err := s.db.GetSpentOutput(vin.Txid, vin.Vout)
if err != nil {
return res, err
}
if stxid != "" {
otx, err := s.chain.GetTransaction(stxid)
if err != nil {
return res, err
}
SpentOutputLoop:
for _, vout := range otx.Vout {
for _, a := range vout.ScriptPubKey.Addresses {
if stringInSlice(a, addr) {
ai.Address = a
hi, ok := ads[a]
if ok {
hi.InputIndexes = append(hi.InputIndexes, int(vout.N))
} else {
hi := addressHistoryIndexes{}
hi.InputIndexes = append(hi.InputIndexes, int(vout.N))
hi.OutputIndexes = make([]int, 0)
ads[a] = hi
}
break SpentOutputLoop
}
}
}
}
hi = append(hi, ai)
}
for _, vout := range tx.Vout {
ao := addressHistoryOutputs{
Satoshis: int64(vout.Value * 10E8),
Script: vout.ScriptPubKey.Hex,
ScriptAsm: vout.ScriptPubKey.Asm,
SpentIndex: int(vout.N),
}
for _, a := range vout.ScriptPubKey.Addresses {
if stringInSlice(a, addr) {
ao.Address = a
hi, ok := ads[a]
if ok {
hi.OutputIndexes = append(hi.OutputIndexes, int(vout.N))
} else {
hi := addressHistoryIndexes{}
hi.InputIndexes = make([]int, 0)
hi.OutputIndexes = append(hi.OutputIndexes, int(vout.N))
ads[a] = hi
}
break
}
}
ho = append(ho, ao)
}
ahi := addressHistoryItem{}
ahi.Addresses = ads
ahi.Confirmations = int(tx.Confirmations)
ahi.Tx.BlockHash = tx.BlockHash
ahi.Tx.BlockTimestamp = tx.Blocktime
// ahi.Tx.FeeSatoshis
ahi.Tx.Hash = tx.Txid
ahi.Tx.Height = int(bestheight) - ahi.Confirmations
ahi.Tx.Hex = tx.Hex
ahi.Tx.Inputs = hi
// ahi.Tx.InputSatoshis
ahi.Tx.Locktime = int(tx.LockTime)
ahi.Tx.Outputs = ho
// ahi.Tx.OutputSatoshis
ahi.Tx.Version = int(tx.Version)
res.Result.Items = append(res.Result.Items, ahi)
}
}
return
}
func unmarshalArray(params []byte, np int) (p []interface{}, err error) {
err = json.Unmarshal(params, &p)
if err != nil {

View File

@ -51,6 +51,56 @@
return socket.send({ method, params }, f);
}
function getAddressHistory() {
var addresses = document.getElementById('getAddressHistoryAddresses').value.split(",");
var mempool = document.getElementById("getAddressHistoryMempool").checked;
lookupAddressHistories(addresses, 0, 1, mempool, 2000000, 0, function (result) {
console.log('getAddressHistory sent successfully');
console.log(result);
document.getElementById('getAddressHistoryResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
});
}
function lookupAddressHistories(addresses, from, to, mempool, start, end, f) {
const method = 'getAddressHistory';
const rangeParam = mempool ? {
start, // needed for older bitcores (so we don't load all history if bitcore-node < 3.1.3)
end,
queryMempoolOnly: true,
} : {
start,
end,
queryMempol: false,
};
const params = [
addresses,
{
...rangeParam,
from,
to,
},
];
return socket.send({ method, params }, f);
}
function lookupTransactionsIdsMempool(addresses, mempool, start, end, f) {
const method = 'getAddressTxids';
const rangeParam = mempool ? {
start,
end,
queryMempoolOnly: true,
} : {
start,
end,
queryMempol: false,
};
const params = [
addresses,
rangeParam,
];
return socket.send({ method, params }, f);
}
function getBlockHeader() {
var height = document.getElementById('getBlockHeaderHeight').value;
lookupBlockHash(parseInt(height), function (result) {
@ -120,7 +170,7 @@
<input class="btn btn-secondary" type="button" value="getAddressTxids" onclick="getAddressTxids()">
</div>
<div class="col-8">
<input type="text" class="form-control" id="getAddressTxidsAddresses" value="2MxvKTW83yhVsGCr4BSRqmhca7r8TB2dgW8,bc1qrsf2l34jvqnq0lduyz0j5pfu2nkd93nnq0qggn">
<input type="text" class="form-control" id="getAddressTxidsAddresses" value="2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp,2Mt7P2BAfE922zmfXrdcYTLyR7GUvbwSEns">
</div>
<div class="col form-inline">
<input type="checkbox" id="getAddressTxidsMempool">&nbsp;
@ -131,6 +181,22 @@
<div class="col" id="getAddressTxidsResult">
</div>
</div>
<div class="row">
<div class="col">
<input class="btn btn-secondary" type="button" value="getAddressHistory" onclick="getAddressHistory()">
</div>
<div class="col-8">
<input type="text" class="form-control" id="getAddressHistoryAddresses" value="2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp,2Mt7P2BAfE922zmfXrdcYTLyR7GUvbwSEns">
</div>
<div class="col form-inline">
<input type="checkbox" id="getAddressHistoryMempool">&nbsp;
<label>only mempool</label>
</div>
</div>
<div class="row">
<div class="col" id="getAddressHistoryResult">
</div>
</div>
<div class="row">
<div class="col">
<input class="btn btn-secondary" type="button" value="getBlockHeader" onclick="getBlockHeader()">