Skip to content

Commit

Permalink
Changed program name, switch to testnet, GUI sucks right now
Browse files Browse the repository at this point in the history
  • Loading branch information
etotheipi committed Dec 10, 2011
1 parent 4746c92 commit ca826c4
Show file tree
Hide file tree
Showing 13 changed files with 508 additions and 123 deletions.
182 changes: 151 additions & 31 deletions BitcoinArmoryQt.py → ArmoryQt.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#! /usr/bin/python
################################################################################
#
# Copyright (C) 2011, Alan C. Reiner <[email protected]>
Expand All @@ -7,7 +6,7 @@
#
################################################################################
#
# Project: BitcoinArmory (https://github.com/etotheipi/BitcoinArmory)
# Project: Armory (https://github.com/etotheipi/BitcoinArmory)
# Author: Alan Reiner
# Orig Date: 20 November, 2011
# Descr: This file serves as an engine for python-based Bitcoin software.
Expand Down Expand Up @@ -35,21 +34,13 @@
from PyQt4.QtGui import *

# 8000 lines of python to help us out...
from btcarmoryengine import *
from armoryengine import *
from armorymodels import *

# All the twisted/networking functionality
from twisted.internet.protocol import Protocol, ClientFactory
from twisted.internet.defer import Deferred

# This is an amazing trick for create enum-like dictionaries.
# Either automatically numbers (*args), or name-val pairs (**kwargs)
#http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python
def enum(*sequential, **named):
enums = dict(zip(sequential, range(len(sequential))), **named)
return type('Enum', (), enums)

TXTBL = enum("Status", "Date", "Direction", "Address", "Amount")


SETTINGS_PATH = os.path.join(ARMORY_HOME_DIR, 'ArmorySettings.txt')
Expand All @@ -71,6 +62,7 @@ def __init__(self, parent=None, settingsPath=None):

self.loadWalletsAndSettings()
self.setupNetworking()
self.loadBlockchain()

self.lblAvailWlt = QLabel('Available Wallets:')
self.lblAvailWlt.setAlignment(Qt.AlignBottom)
Expand All @@ -82,6 +74,7 @@ def __init__(self, parent=None, settingsPath=None):
self.setWindowTitle('Armory - Bitcoin Wallet Management')
self.setWindowIcon(QIcon('icons/armory_logo_32x32.png'))

# Table for all the wallets
self.walletModel = WalletDispModel(self)
self.walletView = QTableView()
self.walletView.setModel(self.walletModel)
Expand All @@ -96,17 +89,29 @@ def __init__(self, parent=None, settingsPath=None):
self.walletView.hideColumn(0)
self.walletView.horizontalHeader().resizeSection(1, 200)

# Table to display ledger/activity
self.ledgerModel = ActivityDispModel(self)
self.ledgerView = QTableView()
self.ledgerView.setModel(self.ledgerModel)
self.ledgerView.setSelectionBehavior(QTableView.SelectRows)
self.ledgerView.setSelectionMode(QTableView.SingleSelection)
self.ledgerView.horizontalHeader().setStretchLastSection(True)
self.ledgerView.verticalHeader().setDefaultSectionSize(25)
self.ledgerView.horizontalHeader().resizeSection(1, 150)


layout = QGridLayout()
layout.addWidget(QLabel("Available Wallets:"), 0, 0, 1, 1)
layout.addWidget(self.walletView, 2, 0, 1, 2)
layout.addWidget(self.lblLogoIcon, 0, 1, 1, 1)
layout.addWidget(self.lblLogoIcon, 0, 2, 1, 1)
layout.addWidget(QLabel("Available Wallets:"), 1, 0, 1, 1)
layout.addWidget(self.walletView, 2, 0, 3, 2)
layout.addWidget(QLabel("Transactions:"), 5, 0, 1, 1)
layout.addWidget(self.ledgerView, 6, 0, 3, 3)

# Attach the layout to the frame that will become the central widget
mainFrame = QFrame()
mainFrame.setLayout(layout)
self.setCentralWidget(mainFrame)
self.setMinimumSize(500,300)
self.setMinimumSize(700,700)

self.statusBar().showMessage('Blockchain loading, please wait...')

Expand All @@ -122,11 +127,13 @@ def restartConnection(protoObj, failReason):
print '! Trying to restart connection !'
reactor.connectTCP(protoObj.peer[0], protoObj.peer[1], self.NetworkingFactory)

self.NetworkingFactory = BitcoinArmoryClientFactory( \
self.NetworkingFactory = ArmoryClientFactory( \
func_loseConnect=restartConnection)
#reactor.connectTCP('127.0.0.1', BITCOIN_PORT, self.NetworkingFactory)




#############################################################################
def loadWalletsAndSettings(self):
self.settings = SettingsFile(self.settingsPath)
Expand All @@ -146,41 +153,57 @@ def loadWalletsAndSettings(self):
else:
self.usermode = UserMode.Standard

# Load wallets found in the .bitcoinarmory directory
# Load wallets found in the .armory directory
wltPaths = self.settings.get('Other_Wallets', expectList=True)
self.walletMap = {}
self.walletIDSet = set()
self.walletIDList = [] # Also need an easily, deterministically-iterable list
self.walletBalances = [] # Also need an easily, deterministically-iterable list
self.walletIndices = {}
self.walletIDSet = set()

# I need some linear lists for accessing by index
self.walletIDList = []
self.walletBalances = []
self.walletSubLedgers = []
self.walletLedgers = []
self.combinedLedger = []
self.ledgerSize = 0

self.latestBlockNum = 0

# Use this store IDs of wallets that are watching-only,
self.walletOfflines = set()

print 'Loading wallets...'
for root,subs,files in os.walk(ARMORY_HOME_DIR):
for f in files:
if f.startswith('armory_') and f.endswith('.wallet') and \
not f.endswith('backup.wallet') and not ('unsuccessful' in f):
wltPaths.append(os.path.join(root, f))
for f in os.listdir(ARMORY_HOME_DIR):
if f.startswith('armory_') and f.endswith('.wallet') and \
not f.endswith('backup.wallet') and not ('unsuccessful' in f):
wltPaths.append(os.path.join(root, f))


wltExclude = self.settings.get('Excluded_Wallets', expectList=True)
for index,fpath in enumerate(wltPaths):
wltOffline = self.settings.get('Offline_WalletIDs', expectList=True)
for fpath in wltPaths:
try:
wltLoad = PyBtcWallet().readWalletFile(fpath)
wltID = wltLoad.wltUniqueIDB58
if wltID in wltExclude:
if fpath in wltExclude:
continue

if wltID in self.walletIDSet:
print '***WARNING: Duplicate wallet detected,', wltID
print ' '*10, 'Wallet 1 (loaded): ', self.walletMap[wltID].walletPath
print ' '*10, 'Wallet 2 (skipped):', fpath
else:
# Update the maps/dictionaries
self.walletMap[wltID] = wltLoad
self.walletIndices[wltID] = len(self.walletMap)-1

# Maintain some linear lists of wallet info
self.walletIDSet.add(wltID)
self.walletIDList.append(wltID)
self.walletBalances.append(-1)
self.walletIndices[wltID] = index

if wltID in wltOffline or fpath in wltOffline:
self.walletOfflines.add(wltID)
except:
print '***WARNING: Wallet could not be loaded:', fpath
print ' skipping... '
Expand All @@ -207,23 +230,112 @@ def getWalletForAddr160(self, addr160):
#############################################################################
def loadBlockchain(self):
print 'Loading blockchain'

BDM_LoadBlockchainFile()

# Now that theb blockchain is loaded, let's populate the wallet info
if TheBDM.isInitialized():
self.statusBar().showMessage('Syncing wallets with blockchain...')
print 'Syncing wallets with blockchain...'
for wltID, wlt in self.walletMap.iteritems():
print 'Syncing', wltID
self.walletMap[wltID].setBlockchainSyncFlag(BLOCKCHAIN_READONLY)
self.walletMap[wltID].syncWithBlockchain()
index = self.walletIndices[wltID]
self.walletBalances[index] = self.walletMap[wltID].getBalance()

# We need to mirror all blockchain & wallet data in linear lists
wltIndex = self.walletIndices[wltID]

self.walletBalances[wltIndex] = wlt.getBalance()
self.walletSubLedgers.append([])
for addrIndex,addr in enumerate(wlt.getAddrList()):
addr20 = addr.getAddr160()
self.walletSubLedgers[-1].append(wlt.getTxLedger(addr20))

t = wlt.getTxLedger()
print wltID, len(t)
self.walletLedgers.append(wlt.getTxLedger())

self.createCombinedLedger()
self.ledgerSize = len(self.combinedLedger)
self.latestBlockNum = TheBDM.getTopBlockHeader().getBlockHeight()
print len(self.combinedLedger), self.latestBlockNum
self.statusBar().showMessage('Blockchain loaded, wallets sync\'d!', 10000)
else:
self.statusBar().showMessage('! Blockchain loading failed !', 10000)

# This will force the table to refresh with new data
self.walletView.selectRow(-1)
#for row in range(len(self.walletMap)):
#self.walletView.selectRow(row)
#self.walletView.selectRow(0)

def createZeroConfLedger(self, wlt):
"""
This is kind of hacky, but I don't want to disrupt the C++ code
too much to implement a *proper* solution... which is that I need
to find a way to process zero-confirmation transactions and produce
ledger entries for them, the same as all the other [past] txs.
So, I added TxRef::getLedgerEntriesForZeroConfTxList to the C++ code
(name was created to be annoying so maybe I remove/replace later).
Then we carefully create TxRef objects to pass into it and copy out
the resulting list. But since these are TxREF objects, they need
to point to persistent memory, which is why the following loops are
weird: they are guaranteed to create data once, and not move it
around in memory, so that my TxRef objects don't get mangled. We
only need them long enough to get the vector<LedgerEntry> result.
(to be more specific, I'm pretty sure this should work no matter
how wacky python's memory mgmt is, unless it moves list data around
in memory between calls)
"""
# We are starting with a map of PyTx objects
zcMap = self.NetworkingFactory.zeroConfTx
timeMap = self.NetworkingFactory.zeroConfTxTime
#print 'ZeroConfListSize:', len(zcMap)
zcTxBinList = []
zcTxRefList = []
zcTxRefPtrList = vector_TxRefPtr(0)
zcTxTimeList = []
# Create persistent list of serialized Tx objects (language-agnostic)
for zchash in zcMap.keys():
zcTxBinList.append( zcMap[zchash].serialize() )
zcTxTimeList.append(timeMap[zchash])
# Create list of TxRef objects
for zc in zcTxBinList:
zcTxRefList.append( TxRef(zc) )
# Python will cast to pointers when we try to add to a vector<TxRef*>
for zc in zcTxRefList:
zcTxRefPtrList.push_back(zc)

# At this point, we will get a vector<LedgerEntry> list and TxRefs
# can safely go out of scope
return wlt.cppWallet.getLedgerEntriesForZeroConfTxList(zcTxRefPtrList)


def createCombinedLedger(self, wltIDList=None, withZeroConf=True):
"""
Create a ledger to display on the main screen, that consists of ledger
entries of any SUBSET of available wallets.
"""
start = RightNow()
if wltIDList==None:
wltIDList = self.walletIDList

self.combinedLedger = []
#for wltID,wlt in self.walletMap.iteritems():
for wltID in wltIDList:
wlt = self.walletMap[wltID]
index = self.walletIndices[wltID]
# Make sure the ledgers are up to date and then combine and sort
#self.walletLedgers[index] = self.walletMap[wltID].getTxLedger()
id_le_pairs = [ [wltID, le] for le in self.walletLedgers[index] ]
id_le_zcpairs = [ [wltID, le] for le in self.createZeroConfLedger(wlt)]
self.combinedLedger.extend(id_le_pairs)
self.combinedLedger.extend(id_le_zcpairs)

self.combinedLedger.sort(key=lambda x:x[1], reverse=True)
print 'Combined ledger:', (RightNow()-start), 'sec', len(self.combinedLedger)


def Heartbeat(self, nextBeatSec=3):
"""
Expand All @@ -236,8 +348,15 @@ def Heartbeat(self, nextBeatSec=3):
newBlks = TheBDM.readBlkFileUpdate()
if newBlks>0:
pass # do something eventually
else:
self.latestBlockNum = TheBDM.getTopBlockHeader().getBlockHeight()



# Check for new tx in the zeroConf pool
self.createCombinedLedger()

"""
self.txNotInBlkchainYet = []
if TheBDM.isInitialized():
for hsh,tx in self.NetworkingFactory.zeroConfTx.iteritems():
Expand All @@ -252,6 +371,7 @@ def Heartbeat(self, nextBeatSec=3):
for tx in self.txNotInBlkchainYet:
print ' ',binary_to_hex(tx)
"""


for wltID, wlt in self.walletMap.iteritems():
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

********************************************************************************

PyBtcEngine -- C++/Python/SWIG backend for developing Bitcoin software
Armory -- Bitcoin Client Software

Copyright (C) 2011, Alan C. Reiner <[email protected]>

Expand Down
6 changes: 3 additions & 3 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

********************************************************************************
*
* Project: BitcoinArmory
* Project: Armory
* Author: Alan Reiner
* Orig Date: 20 November, 2011
* Descr: This file serves as an engine for python-based Bitcoin software.
Expand Down Expand Up @@ -201,7 +201,7 @@

-- TxDPs: Based on BIP 0010:
https://github.com/genjix/bips/blob/master/bip-0010.md
All transaction operations in BitcoinArmory go through
All transaction operations in Armory go through
"Tx Distribution Proposals", which is an ASCII-able
serialization for unsigned transactions, which can be
easily transmitted through email, and used to collect
Expand Down Expand Up @@ -259,7 +259,7 @@
it ... unless you have double-broadcast detection!

A master list of TxOuts waiting to enter the blockchain
is maintained in the BitcoinArmoryClientFactory, and a
is maintained in the ArmoryClientFactory, and a
flag is raised if a tx is received which tries to spend
any outputs already in the queue. The client will usually
just drop the tx because it conflicts with another one in
Expand Down
6 changes: 3 additions & 3 deletions Using_PyBtcEngine.README → Using_Armory.README
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## ##
################################################################################

PyBtcEngine contains over 7,000 lines of code, between the C++ and python
Armory contains over 15,000 lines of code, between the C++ and python
libraries. This can be very confusing for someone unfamiliar with the
code (you). Below I have attempted to illustrate the CONOPS (concept of
operations) that the library was designed for, so you know how to use it
Expand All @@ -16,7 +16,7 @@ the following three files:
[**SWIG**] unittest.py (this has almost everything!)

[ C++ ] cppForSwig/BlockUtilsTest.cpp
[ Python ] btcarmoryengine.py
[ Python ] armoryengine.py

But of course, sample code alone does not make great documentation. I will
attempt to provide reference info for everything else you need to know, here.
Expand Down Expand Up @@ -114,7 +114,7 @@ that all features with an X in either column are accessible in SWIG.
# available functionality in the libraries. I will update this as soon
# as I get the first client release out of the way. For now, you can
# can see most of the C++ methods below (accessed via Python), and then
# do an "import btcarmoryengine; help(btcarmoryengine)" to see a MASSIVE
# do an "import armoryengine; help(armoryengine)" to see a MASSIVE
# list of the available classes and methods. Seriously... it's huge.
#
################################################################################
Expand Down
Loading

0 comments on commit ca826c4

Please sign in to comment.