diff --git a/db/db_get.go b/db/db_get.go index 847d2ec..e4a8298 100644 --- a/db/db_get.go +++ b/db/db_get.go @@ -67,6 +67,31 @@ func (db *ReadOnlyDBColumnFamily) GetBlockHash(height uint32) ([]byte, error) { return rawValue, nil } +func (db *ReadOnlyDBColumnFamily) GetBlockTXs(height uint32) ([]*chainhash.Hash, error) { + handle, err := db.EnsureHandle(prefixes.BlockTXs) + if err != nil { + return nil, err + } + + key := prefixes.BlockTxsKey{ + Prefix: []byte{prefixes.BlockTXs}, + Height: height, + } + slice, err := db.DB.GetCF(db.Opts, handle, key.PackKey()) + defer slice.Free() + if err != nil { + return nil, err + } + if slice.Size() == 0 { + return nil, nil + } + + rawValue := make([]byte, len(slice.Data())) + copy(rawValue, slice.Data()) + value := prefixes.BlockTxsValueUnpack(rawValue) + return value.TxHashes, nil +} + func (db *ReadOnlyDBColumnFamily) GetHeader(height uint32) ([]byte, error) { handle, err := db.EnsureHandle(prefixes.Header) if err != nil { @@ -797,7 +822,6 @@ func (db *ReadOnlyDBColumnFamily) getMempoolTx(txhash *chainhash.Hash) ([]byte, return value.RawTx, &msgTx, nil } - func (db *ReadOnlyDBColumnFamily) GetTxCount(height uint32) (*prefixes.TxCountValue, error) { handle, err := db.EnsureHandle(prefixes.TxCount) if err != nil { @@ -844,6 +868,98 @@ func (db *ReadOnlyDBColumnFamily) GetTxHeight(txhash *chainhash.Hash) (uint32, e return height, nil } +type TxMerkle struct { + TxHash *chainhash.Hash + RawTx []byte + Height int + Pos uint32 + Merkle []*chainhash.Hash +} + +// merklePath selects specific transactions by position within blockTxs. +// The resulting merkle path (aka merkle branch, or merkle) is a list of TX hashes +// which are in sibling relationship with TX nodes on the path to the root. +func merklePath(pos uint32, blockTxs, partial []*chainhash.Hash) []*chainhash.Hash { + parent := func(p uint32) uint32 { + return p >> 1 + } + sibling := func(p uint32) uint32 { + if p%2 == 0 { + return p + 1 + } else { + return p - 1 + } + } + p := parent(pos) + if p == 0 { + // No parent, path is complete. + return partial + } + // Add sibling to partial path and proceed to parent TX. + return merklePath(p, blockTxs, append(partial, blockTxs[sibling(pos)])) +} + +func (db *ReadOnlyDBColumnFamily) GetTxMerkle(tx_hashes []chainhash.Hash) ([]TxMerkle, error) { + + selectedTxNum := make([]*IterOptions, 0, len(tx_hashes)) + for _, txhash := range tx_hashes { + key := prefixes.TxNumKey{Prefix: []byte{prefixes.TxNum}, TxHash: &txhash} + opt, err := db.selectFrom(key.Prefix, &key, &key) + if err != nil { + return nil, err + } + selectedTxNum = append(selectedTxNum, opt...) + } + + selectTxByTxNum := func(in []*prefixes.PrefixRowKV) ([]*IterOptions, error) { + txNumKey := in[0].Key.(*prefixes.TxNumKey) + out := make([]*IterOptions, 0, 100) + startKey := &prefixes.TxKey{ + Prefix: []byte{prefixes.Tx}, + TxHash: txNumKey.TxHash, + } + endKey := &prefixes.TxKey{ + Prefix: []byte{prefixes.Tx}, + TxHash: txNumKey.TxHash, + } + selectedTx, err := db.selectFrom([]byte{prefixes.Tx}, startKey, endKey) + if err != nil { + return nil, err + } + out = append(out, selectedTx...) + return out, nil + } + + blockTxsCache := make(map[uint32][]*chainhash.Hash) + results := make([]TxMerkle, 0, 500) + for kvs := range innerJoin(db.DB, iterate(db.DB, selectedTxNum), selectTxByTxNum) { + if err := checkForError(kvs); err != nil { + return results, err + } + txNumKey, txNumVal := kvs[0].Key.(*prefixes.TxNumKey), kvs[0].Value.(*prefixes.TxNumValue) + _, txVal := kvs[1].Key.(*prefixes.TxKey), kvs[1].Value.(*prefixes.TxValue) + txHeight := stack.BisectRight(db.TxCounts, []uint32{txNumVal.TxNum})[0] + txPos := txNumVal.TxNum - db.TxCounts.Get(txHeight-1) + // We need all the TX hashes in order to select out the relevant ones. + if _, ok := blockTxsCache[txHeight]; !ok { + txs, err := db.GetBlockTXs(txHeight) + if err != nil { + return results, err + } + blockTxsCache[txHeight] = txs + } + blockTxs, _ := blockTxsCache[txHeight] + results = append(results, TxMerkle{ + TxHash: txNumKey.TxHash, + RawTx: txVal.RawTx, + Height: int(txHeight), + Pos: txPos, + Merkle: merklePath(txPos, blockTxs, []*chainhash.Hash{}), + }) + } + return results, nil +} + func (db *ReadOnlyDBColumnFamily) GetDBState() (*prefixes.DBStateValue, error) { handle, err := db.EnsureHandle(prefixes.DBState) if err != nil { diff --git a/server/jsonrpc_blockchain.go b/server/jsonrpc_blockchain.go index 2705141..8a18742 100644 --- a/server/jsonrpc_blockchain.go +++ b/server/jsonrpc_blockchain.go @@ -659,12 +659,12 @@ func (s *BlockchainTransactionService) Broadcast(req *TransactionBroadcastReq, r type TransactionGetReq string type TXFullDetail struct { - Height uint32 `json:"block_height"` - Merkle string `json:"merkle"` - Pos uint64 `json:"pos"` + Height int `json:"block_height"` + Pos uint32 `json:"pos"` + Merkle []string `json:"merkle"` } type TXDetail struct { - Height uint32 `json:"block_height"` + Height int `json:"block_height"` } // TransactionResp is a pair consisting of: @@ -701,15 +701,45 @@ type TransactionGetBatchResp []TXGetResp // 'blockchain.transaction.get_batch' func (s *BlockchainTransactionService) Get_batch(req *TransactionGetBatchReq, resp **TransactionGetBatchResp) error { - return nil + if len(*req) > 100 { + return fmt.Errorf("too many tx hashes in request: %v", len(*req)) + } + tx_hashes := make([]chainhash.Hash, len(*req)) + for i, txid := range *req { + if err := chainhash.Decode(&tx_hashes[i], txid); err != nil { + return err + } + } + dbResult, err := s.DB.GetTxMerkle(tx_hashes) + if err != nil { + return err + } + result := make([]TXGetResp, 0, len(dbResult)) + for _, r := range dbResult { + merkles := make([]string, len(r.Merkle)) + for i, m := range r.Merkle { + merkles[i] = m.String() + } + detail := TXFullDetail{ + Height: r.Height, + Pos: r.Pos, + Merkle: merkles, + } + result = append(result, TXGetResp{hex.EncodeToString(r.RawTx), &detail}) + } + *resp = (*TransactionGetBatchResp)(&result) + return err } -type TransactionGetMerkleReq string +type TransactionGetMerkleReq struct { + TxHash string `json:"tx_hash"` + Height uint32 `json:"height"` +} type TransactionGetMerkleResp TXGetResp // 'blockchain.transaction.get_merkle' func (s *BlockchainTransactionService) Get_merkle(req *TransactionGetMerkleReq, resp **TransactionGetMerkleResp) error { - txids := [1]string{string(*req)} + txids := [1]string{string(req.TxHash)} request := TransactionGetBatchReq(txids[:]) var response *TransactionGetBatchResp err := s.Get_batch(&request, &response)