Implement Bitcore socket.io method getAddressHistory
This commit is contained in:
parent
d96af8e648
commit
90a9edda41
@ -45,6 +45,7 @@ func AddressToOutputScript(address string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Tx struct {
|
type Tx struct {
|
||||||
|
Hex string `json:"hex"`
|
||||||
Txid string `json:"txid"`
|
Txid string `json:"txid"`
|
||||||
Version int32 `json:"version"`
|
Version int32 `json:"version"`
|
||||||
LockTime uint32 `json:"locktime"`
|
LockTime uint32 `json:"locktime"`
|
||||||
|
|||||||
@ -112,12 +112,19 @@ type reqRange struct {
|
|||||||
|
|
||||||
var onMessageHandlers = map[string]func(*SocketIoServer, json.RawMessage) (interface{}, error){
|
var onMessageHandlers = map[string]func(*SocketIoServer, json.RawMessage) (interface{}, error){
|
||||||
"\"getAddressTxids\"": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err 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 {
|
if err == nil {
|
||||||
rv, err = s.getAddressTxids(addr, &rr)
|
rv, err = s.getAddressTxids(addr, &rr)
|
||||||
}
|
}
|
||||||
return
|
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) {
|
"\"getBlockHeader\"": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) {
|
||||||
height, hash, err := unmarshalGetBlockHeader(params)
|
height, hash, err := unmarshalGetBlockHeader(params)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -156,7 +163,7 @@ func (s *SocketIoServer) onMessage(c *gosocketio.Channel, req map[string]json.Ra
|
|||||||
return ""
|
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
|
var p []json.RawMessage
|
||||||
err = json.Unmarshal(params, &p)
|
err = json.Unmarshal(params, &p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -211,6 +218,185 @@ func (s *SocketIoServer) getAddressTxids(addr []string, rr *reqRange) ([]string,
|
|||||||
return txids, nil
|
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) {
|
func unmarshalArray(params []byte, np int) (p []interface{}, err error) {
|
||||||
err = json.Unmarshal(params, &p)
|
err = json.Unmarshal(params, &p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -51,6 +51,56 @@
|
|||||||
return socket.send({ method, params }, f);
|
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() {
|
function getBlockHeader() {
|
||||||
var height = document.getElementById('getBlockHeaderHeight').value;
|
var height = document.getElementById('getBlockHeaderHeight').value;
|
||||||
lookupBlockHash(parseInt(height), function (result) {
|
lookupBlockHash(parseInt(height), function (result) {
|
||||||
@ -120,7 +170,7 @@
|
|||||||
<input class="btn btn-secondary" type="button" value="getAddressTxids" onclick="getAddressTxids()">
|
<input class="btn btn-secondary" type="button" value="getAddressTxids" onclick="getAddressTxids()">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<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>
|
||||||
<div class="col form-inline">
|
<div class="col form-inline">
|
||||||
<input type="checkbox" id="getAddressTxidsMempool">
|
<input type="checkbox" id="getAddressTxidsMempool">
|
||||||
@ -131,6 +181,22 @@
|
|||||||
<div class="col" id="getAddressTxidsResult">
|
<div class="col" id="getAddressTxidsResult">
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
|
<label>only mempool</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" id="getAddressHistoryResult">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input class="btn btn-secondary" type="button" value="getBlockHeader" onclick="getBlockHeader()">
|
<input class="btn btn-secondary" type="button" value="getBlockHeader" onclick="getBlockHeader()">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user