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")
type WalletConfig struct {
Disable bool `mapstructure:"disable"`
Rescan bool `mapstructure:"rescan"`
+ TxIndex bool `mapstructure:"txindex"`
MaxTxFee uint64 `mapstructure:"max_tx_fee"`
}
return &WalletConfig{
Disable: false,
Rescan: false,
+ TxIndex: false,
MaxTxFee: uint64(1000000000),
}
}
"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"
"github.com/bytom/p2p"
"github.com/bytom/protocol"
w "github.com/bytom/wallet"
- dbm "github.com/bytom/database/leveldb"
- "github.com/bytom/database"
)
const (
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")
}
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"
"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 (
GlobalTxIndexPrefix = "GTID:"
)
+var errAccntTxIDNotFound = errors.New("account TXID not found")
+
func formatKey(blockHeight uint64, position uint32) string {
return fmt.Sprintf("%016x%08x", blockHeight, position)
}
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
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
// 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{}
}
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)
"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 (
DB dbm.DB
rw sync.RWMutex
status StatusInfo
+ TxIndexFlag bool
AccountMgr *account.Manager
AssetReg *asset.Registry
Hsm *pseudohsm.HSM
}
//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,
RecoveryMgr: newRecoveryManager(walletDB, account),
eventDispatcher: dispatcher,
rescanCh: make(chan struct{}, 1),
+ TxIndexFlag: txIndexFlag,
}
if err := w.loadWalletInfo(); err != nil {
"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(".", "")
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 {
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)
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)
}
//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)
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,
chain: chain,
RecoveryMgr: newRecoveryManager(walletDB, account),
eventDispatcher: dispatcher,
+ TxIndexFlag: txIndexFlag,
}
wallet.txMsgSub, _ = wallet.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
return wallet