Commit eab3a1dc authored by HAOYUatHZ's avatar HAOYUatHZ Committed by Paladz

feat: support global tx in api.listTransactions and api.getTransaction (#1694)

* feat: add wallet.SaveGlobalTxIdxFlag

* feat: add 	blockHash, position, err := parseGlobalTxIdx(string(globalTxIdx))

* feat: implement parseGlobalTxIdx

* wip

* fix: fix calcGlobalTxIndex()

* feat: support getGlobalTxByTxID() in GetTransactionByTxID()

* refactor: rename save_global_tx_index

* refactor: use encoding/binary for position in txIndex

* feat: use wallet.TxIndexFlag to control GetTransactionByTxID flow

* refactor: wrap txIndexFlag inside NewWallet()

* fix: fix wallet_test_util

* refactor: reduce globalTxIdx space by using bytes directly
parent da28b597
......@@ -28,6 +28,7 @@ func init() {
runNodeCmd.Flags().Bool("wallet.disable", config.Wallet.Disable, "Disable wallet")
runNodeCmd.Flags().Bool("wallet.rescan", config.Wallet.Rescan, "Rescan wallet")
runNodeCmd.Flags().Bool("wallet.txindex", config.Wallet.TxIndex, "Save global tx index")
runNodeCmd.Flags().Bool("vault_mode", config.VaultMode, "Run in the offline enviroment")
runNodeCmd.Flags().Bool("web.closed", config.Web.Closed, "Lanch web browser or not")
runNodeCmd.Flags().String("chain_id", config.ChainID, "Select network type")
......
......@@ -177,6 +177,7 @@ func DefaultP2PConfig() *P2PConfig {
type WalletConfig struct {
Disable bool `mapstructure:"disable"`
Rescan bool `mapstructure:"rescan"`
TxIndex bool `mapstructure:"txindex"`
MaxTxFee uint64 `mapstructure:"max_tx_fee"`
}
......@@ -216,6 +217,7 @@ func DefaultWalletConfig() *WalletConfig {
return &WalletConfig{
Disable: false,
Rescan: false,
TxIndex: false,
MaxTxFee: uint64(1000000000),
}
}
......
......@@ -22,6 +22,8 @@ import (
"github.com/bytom/blockchain/txfeed"
cfg "github.com/bytom/config"
"github.com/bytom/consensus"
"github.com/bytom/database"
dbm "github.com/bytom/database/leveldb"
"github.com/bytom/env"
"github.com/bytom/event"
"github.com/bytom/mining/cpuminer"
......@@ -32,8 +34,6 @@ import (
"github.com/bytom/p2p"
"github.com/bytom/protocol"
w "github.com/bytom/wallet"
dbm "github.com/bytom/database/leveldb"
"github.com/bytom/database"
)
const (
......@@ -109,7 +109,7 @@ func NewNode(config *cfg.Config) *Node {
walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())
accounts = account.NewManager(walletDB, chain)
assets = asset.NewRegistry(walletDB, chain)
wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain, dispatcher)
wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain, dispatcher, config.Wallet.TxIndex)
if err != nil {
log.WithFields(log.Fields{"module": logModule, "error": err}).Error("init NewWallet")
}
......
......@@ -260,7 +260,7 @@ func (cfg *walletTestConfig) Run() error {
accountManager := account.NewManager(walletDB, chain)
assets := asset.NewRegistry(walletDB, chain)
dispatcher := event.NewDispatcher()
wallet, err := w.NewWallet(walletDB, accountManager, assets, hsm, chain, dispatcher)
wallet, err := w.NewWallet(walletDB, accountManager, assets, hsm, chain, dispatcher, false)
if err != nil {
return err
}
......
package wallet
import (
"encoding/binary"
"encoding/json"
"fmt"
"sort"
......@@ -11,10 +12,11 @@ import (
"github.com/bytom/asset"
"github.com/bytom/blockchain/query"
"github.com/bytom/crypto/sha3pool"
dbm "github.com/bytom/database/leveldb"
chainjson "github.com/bytom/encoding/json"
"github.com/bytom/errors"
"github.com/bytom/protocol/bc"
"github.com/bytom/protocol/bc/types"
dbm "github.com/bytom/database/leveldb"
)
const (
......@@ -26,6 +28,8 @@ const (
GlobalTxIndexPrefix = "GTID:"
)
var errAccntTxIDNotFound = errors.New("account TXID not found")
func formatKey(blockHeight uint64, position uint32) string {
return fmt.Sprintf("%016x%08x", blockHeight, position)
}
......@@ -46,8 +50,19 @@ func calcGlobalTxIndexKey(txID string) []byte {
return []byte(GlobalTxIndexPrefix + txID)
}
func calcGlobalTxIndex(blockHash *bc.Hash, position int) []byte {
return []byte(fmt.Sprintf("%064x%08x", blockHash.String(), position))
func calcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
txIdx := make([]byte, 40)
copy(txIdx[:32], blockHash.Bytes())
binary.BigEndian.PutUint64(txIdx[32:], position)
return txIdx
}
func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) {
var hashBytes [32]byte
copy(hashBytes[:], globalTxIdx[:32])
hash := bc.NewHash(hashBytes)
position := binary.BigEndian.Uint64(globalTxIdx[32:])
return &hash, position
}
// deleteTransaction delete transactions when orphan block rollback
......@@ -124,9 +139,13 @@ func (w *Wallet) indexTransactions(batch dbm.Batch, b *types.Block, txStatus *bc
batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
}
if !w.TxIndexFlag {
return nil
}
for position, globalTx := range b.Transactions {
blockHash := b.BlockHeader.Hash()
batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, position))
batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position)))
}
return nil
......@@ -166,21 +185,57 @@ transactionLoop:
// GetTransactionByTxID get transaction by txID
func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
return annotatedTx, nil
} else if !w.TxIndexFlag {
return nil, err
}
return w.getGlobalTxByTxID(txID)
}
func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
annotatedTx := &query.AnnotatedTx{}
formatKey := w.DB.Get(calcTxIndexKey(txID))
if formatKey == nil {
return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
return nil, errAccntTxIDNotFound
}
annotatedTx := &query.AnnotatedTx{}
txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
return nil, err
}
annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
return annotatedTx, nil
}
func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
globalTxIdx := w.DB.Get(calcGlobalTxIndexKey(txID))
if globalTxIdx == nil {
return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
}
blockHash, pos := parseGlobalTxIdx(globalTxIdx)
block, err := w.chain.GetBlockByHash(blockHash)
if err != nil {
return nil, err
}
txStatus, err := w.chain.GetTransactionStatus(blockHash)
if err != nil {
return nil, err
}
statusFail, err := txStatus.GetStatus(int(pos))
if err != nil {
return nil, err
}
tx := block.Transactions[int(pos)]
return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
}
// GetTransactionsSummary get transactions summary
func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
Txs := []TxSummary{}
......
......@@ -58,7 +58,7 @@ func TestWalletUnconfirmedTxs(t *testing.T) {
}
dispatcher := event.NewDispatcher()
w := mockWallet(testDB, accountManager, reg, nil, dispatcher)
w := mockWallet(testDB, accountManager, reg, nil, dispatcher, false)
utxos := []*account.UTXO{}
btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
utxos = append(utxos, btmUtxo)
......
......@@ -9,12 +9,12 @@ import (
"github.com/bytom/account"
"github.com/bytom/asset"
"github.com/bytom/blockchain/pseudohsm"
dbm "github.com/bytom/database/leveldb"
"github.com/bytom/errors"
"github.com/bytom/event"
"github.com/bytom/protocol"
"github.com/bytom/protocol/bc"
"github.com/bytom/protocol/bc/types"
dbm "github.com/bytom/database/leveldb"
)
const (
......@@ -45,6 +45,7 @@ type Wallet struct {
DB dbm.DB
rw sync.RWMutex
status StatusInfo
TxIndexFlag bool
AccountMgr *account.Manager
AssetReg *asset.Registry
Hsm *pseudohsm.HSM
......@@ -57,7 +58,7 @@ type Wallet struct {
}
//NewWallet return a new wallet instance
func NewWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, hsm *pseudohsm.HSM, chain *protocol.Chain, dispatcher *event.Dispatcher) (*Wallet, error) {
func NewWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, hsm *pseudohsm.HSM, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) (*Wallet, error) {
w := &Wallet{
DB: walletDB,
AccountMgr: account,
......@@ -67,6 +68,7 @@ func NewWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry,
RecoveryMgr: newRecoveryManager(walletDB, account),
eventDispatcher: dispatcher,
rescanCh: make(chan struct{}, 1),
TxIndexFlag: txIndexFlag,
}
if err := w.loadWalletInfo(); err != nil {
......
......@@ -24,6 +24,26 @@ import (
"github.com/bytom/protocol/bc/types"
)
func TestEncodeDecodeGlobalTxIndex(t *testing.T) {
want := &struct {
BlockHash bc.Hash
Position uint64
}{
BlockHash: bc.NewHash([32]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}),
Position: 1,
}
globalTxIdx := calcGlobalTxIndex(&want.BlockHash, want.Position)
blockHashGot, positionGot := parseGlobalTxIdx(globalTxIdx)
if *blockHashGot != want.BlockHash {
t.Errorf("blockHash mismatch. Get: %v. Expect: %v", *blockHashGot, want.BlockHash)
}
if positionGot != want.Position {
t.Errorf("position mismatch. Get: %v. Expect: %v", positionGot, want.Position)
}
}
func TestWalletVersion(t *testing.T) {
// prepare wallet
dirPath, err := ioutil.TempDir(".", "")
......@@ -36,7 +56,7 @@ func TestWalletVersion(t *testing.T) {
defer os.RemoveAll("temp")
dispatcher := event.NewDispatcher()
w := mockWallet(testDB, nil, nil, nil, dispatcher)
w := mockWallet(testDB, nil, nil, nil, dispatcher, false)
// legacy status test case
type legacyStatusInfo struct {
......@@ -152,7 +172,7 @@ func TestWalletUpdate(t *testing.T) {
txStatus.SetStatus(1, false)
store.SaveBlock(block, txStatus)
w := mockWallet(testDB, accountManager, reg, chain, dispatcher)
w := mockWallet(testDB, accountManager, reg, chain, dispatcher, true)
err = w.AttachBlock(block)
if err != nil {
t.Fatal(err)
......@@ -174,7 +194,7 @@ func TestWalletUpdate(t *testing.T) {
for position, tx := range block.Transactions {
get := w.DB.Get(calcGlobalTxIndexKey(tx.ID.String()))
bh := block.BlockHeader.Hash()
expect := calcGlobalTxIndex(&bh, position)
expect := calcGlobalTxIndex(&bh, uint64(position))
if !reflect.DeepEqual(get, expect) {
t.Fatalf("position#%d: compare retrieved globalTxIdx err", position)
}
......@@ -243,7 +263,7 @@ func TestMemPoolTxQueryLoop(t *testing.T) {
//block := mockSingleBlock(tx)
txStatus := bc.NewTransactionStatus()
txStatus.SetStatus(0, false)
w, err := NewWallet(testDB, accountManager, reg, hsm, chain, dispatcher)
w, err := NewWallet(testDB, accountManager, reg, hsm, chain, dispatcher, false)
go w.memPoolTxQueryLoop()
w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgNewTx}})
time.Sleep(time.Millisecond * 10)
......@@ -300,7 +320,7 @@ func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder
return tplBuilder.Build()
}
func mockWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher) *Wallet {
func mockWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
wallet := &Wallet{
DB: walletDB,
AccountMgr: account,
......@@ -308,6 +328,7 @@ func mockWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry
chain: chain,
RecoveryMgr: newRecoveryManager(walletDB, account),
eventDispatcher: dispatcher,
TxIndexFlag: txIndexFlag,
}
wallet.txMsgSub, _ = wallet.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
return wallet
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment