Pages:
Author

Topic: [ANN] BitcoinArmory-Daemon - armory on web servers - page 3. (Read 20542 times)

newbie
Activity: 53
Merit: 0
Hi

I guess this is really basic, but I still need some help getting this script running.
So I'm running this on a linux machine, I've installed python, and I'm trying to run the script.

$ ./armoryd.py
from: can't read /var/mail/twisted.internet
from: can't read /var/mail/twisted.web
from: can't read /var/mail/txjsonrpc.web
from: can't read /var/mail/txjsonrpc.auth
from: can't read /var/mail/twisted.cred.checkers
from: can't read /var/mail/armoryengine
./armoryd.py: line 51: import: command not found
./armoryd.py: line 52: import: command not found
./armoryd.py: line 53: import: command not found
./armoryd.py: line 54: import: command not found
./armoryd.py: line 55: import: command not found
./armoryd.py: line 56: import: command not found
./armoryd.py: line 59: import: command not found
from: can't read /var/mail/jsonrpc
./armoryd.py: line 61: syntax error near unexpected token `('
./armoryd.py: line 61: `class UniversalEncoder(json.JSONEncoder):'
$

So then I installed twisted:
$ sudo apt-get install python-twisted

But I still get the same message.
Trying to run it on Windows I get the same import problem.

Any suggestions?

Question2
When I get the script running and can use it to create offline transactions etc, can I have it interact with another program in another language I've written?
If so, can I do it over localhost, and send RPC or something, getting replies in json?
mav
full member
Activity: 169
Merit: 107
Great news, web-armory people! The daemon is being included with armory itself so will be up to date and more fully featured than I ever could have achieved.

see https://bitcointalksearch.org/topic/m.1443054 for details.

I will keep this thread open to discuss the particulars of using armory on webservers, as the daemon is not strictly only for use on webservers (may be used for, say, running your uniquely common yet complex task easily, or remotely controlling your armory application).
pvz
newbie
Activity: 53
Merit: 0
The .so most definitely needs to be recompiled to use the new stuff.
You are right, this was the problem.
Thanks!

Status on my Debian 6 server:
- operational bitcoind daemon
- download BitcoinArmory-Daemon (https://github.com/thedawnrider/BitcoinArmory-Daemon.git)
  with latest armoryengine.py by mav
  (added Armory and Armory-Daemon dependencies)
- Replaced some code in armory-daemon.py (line 217 https://bitcointalksearch.org/topic/m.1437378 by etotheipi)
- download and make BitcoinArmory (https://github.com/etotheipi/BitcoinArmory.git)
- replace CppBlockUtils.py and _CppBlockUtils.so on Armory-Daemon from BitcoinArmory
-> start Armory-Daemon ($ python armory-daemon.py)

Now starting to explore how the armory-daemon works... Wink
legendary
Activity: 1428
Merit: 1093
Core Armory Developer
By the way, I am not able to compile the CppBlockUtils.py or _CppBlockUtils.so code.

Then how are you running with the new code?  The .so most definitely needs to be recompiled to use the new stuff.  I would expect seg faults, not hanging, but computers are weird....

I recommend checking out the current Armory branch to a different directory, type "make", and then copy the CppBlockUtils.py and _CppBlockUtils.so over to the BitcoinArmory-Daemon directory.  Then it should work.
pvz
newbie
Activity: 53
Merit: 0
I just browsed your code and didn't see a whole lot that has to be changed, except you need to replace "TheBDM.LoadBlockchain()" with "TheBDM.setBlocking(True);  TheBDM.setOnlineMode(True)".  There might be a couple minor things that you have to update, and please post here with any more issues and I bet we can clear them up quickly.  But overall you don't appear to be using many things that changed.

With this change I get the following error:
Code:
(ERROR) armoryengine.py:11363 - Received inputTuple: GoOnlineRequested [13, 31616721, True]

If I set:
Code:
TheBDM.setBlocking(True);  TheBDM.setOnlineMode(FALSE)

There are no errors, also armory-daemon does not get started.
Well...I think it does not get started because I do not receive this message:
Code:
Loading blockchain
Initialising server
Server started

The process seems like to hang. Most of the time a ^C helps me out, now I have to shut down the terminal.

I expect the online mode needs a TheBDM.setOnlineMode(true) but I can't get to the code triggering the error.

By the way, I am not able to compile the CppBlockUtils.py or _CppBlockUtils.so code.
legendary
Activity: 1428
Merit: 1093
Core Armory Developer
I have recently pushed changes to the daemon to bring it up to date with the latest armoryengine.py

Unfortunately I am unable to load the blockchain, and am getting these errors.

Code:
(ERROR) armoryengine.py:11362 - Error processing BDM input
(ERROR) armoryengine.py:11363 - Received inputTuple: StartScanRequested [8, 28474661, False]
(ERROR) armoryengine.py:11364 - Error processing ID (28474661)
MAV thanks!

I have a fully operational bitcoind daemon (Debian 6) and also get the same errors.

Does the CppBlockUtils.py and/or _CppBlockUtils.so can stay on the same version or do they need a more accurate version also?

That's definitely going to have to be recompiled. 

On that note: mav, did you change any of the C++ code?  Or were you just using the stock armory code, and just putting a wrapper around it?  If you just wrapped it, it may be better to just fork the BitcoinArmory repo and add your .py files to it.  Then updates are just a "make" away, and goes pretty smoothly on all linux distros (few dependencies, and no version requirements on them).

Aaaaack been a long time since I looked at this and forgot to copy the new _CppBlockUtils.so to the repo. I have not changed any c++ code, this is just a wrapper. I am going to put this on hold until the changes are made. Then I will properly re-assess the situation and put this in a fork of BitcoinArmory rather than as a standalone daemon with the .so binary included.

Actually, this may not be so bad.  I was busy preaching about all the changes and how it was all worth the effort given the usability improvement for Armory GUI ... but actually you may be relatively unaffected as long as you set blocking=True.  In general, the GUI is advancing, not the library, so you don't have to upgrade like this often.

I just browsed your code and didn't see a whole lot that has to be changed, except you need to replace "TheBDM.LoadBlockchain()" with "TheBDM.setBlocking(True);  TheBDM.setOnlineMode(True)".  There might be a couple minor things that you have to update, and please post here with any more issues and I bet we can clear them up quickly.  But overall you don't appear to be using many things that changed.
mav
full member
Activity: 169
Merit: 107
I have recently pushed changes to the daemon to bring it up to date with the latest armoryengine.py

Unfortunately I am unable to load the blockchain, and am getting these errors.

Code:
(ERROR) armoryengine.py:11362 - Error processing BDM input
(ERROR) armoryengine.py:11363 - Received inputTuple: StartScanRequested [8, 28474661, False]
(ERROR) armoryengine.py:11364 - Error processing ID (28474661)
MAV thanks!

I have a fully operational bitcoind daemon (Debian 6) and also get the same errors.

Does the CppBlockUtils.py and/or _CppBlockUtils.so can stay on the same version or do they need a more accurate version also?

That's definitely going to have to be recompiled. 

On that note: mav, did you change any of the C++ code?  Or were you just using the stock armory code, and just putting a wrapper around it?  If you just wrapped it, it may be better to just fork the BitcoinArmory repo and add your .py files to it.  Then updates are just a "make" away, and goes pretty smoothly on all linux distros (few dependencies, and no version requirements on them).

Aaaaack been a long time since I looked at this and forgot to copy the new _CppBlockUtils.so to the repo. I have not changed any c++ code, this is just a wrapper. I am going to put this on hold until the changes are made. Then I will properly re-assess the situation and put this in a fork of BitcoinArmory rather than as a standalone daemon with the .so binary included.
legendary
Activity: 1428
Merit: 1093
Core Armory Developer
I have recently pushed changes to the daemon to bring it up to date with the latest armoryengine.py

Unfortunately I am unable to load the blockchain, and am getting these errors.

Code:
(ERROR) armoryengine.py:11362 - Error processing BDM input
(ERROR) armoryengine.py:11363 - Received inputTuple: StartScanRequested [8, 28474661, False]
(ERROR) armoryengine.py:11364 - Error processing ID (28474661)
MAV thanks!

I have a fully operational bitcoind daemon (Debian 6) and also get the same errors.

Does the CppBlockUtils.py and/or _CppBlockUtils.so can stay on the same version or do they need a more accurate version also?

That's definitely going to have to be recompiled. 

On that note: mav, did you change any of the C++ code?  Or were you just using the stock armory code, and just putting a wrapper around it?  If you just wrapped it, it may be better to just fork the BitcoinArmory repo and add your .py files to it.  Then updates are just a "make" away, and goes pretty smoothly on all linux distros (few dependencies, and no version requirements on them).
pvz
newbie
Activity: 53
Merit: 0
I have recently pushed changes to the daemon to bring it up to date with the latest armoryengine.py

Unfortunately I am unable to load the blockchain, and am getting these errors.

Code:
(ERROR) armoryengine.py:11362 - Error processing BDM input
(ERROR) armoryengine.py:11363 - Received inputTuple: StartScanRequested [8, 28474661, False]
(ERROR) armoryengine.py:11364 - Error processing ID (28474661)
MAV thanks!

I have a fully operational bitcoind daemon (Debian 6) and also get the same errors.

Does the CppBlockUtils.py and/or _CppBlockUtils.so can stay on the same version or do they need a more accurate version also?
legendary
Activity: 1428
Merit: 1093
Core Armory Developer
I have recently pushed changes to the daemon to bring it up to date with the latest armoryengine.py

Unfortunately I am unable to load the blockchain, and am getting these errors.

Code:
(ERROR) armoryengine.py:11362 - Error processing BDM input
(ERROR) armoryengine.py:11363 - Received inputTuple: StartScanRequested [8, 28474661, False]
(ERROR) armoryengine.py:11364 - Error processing ID (28474661)

Any ideas why these errors are being thrown? This is happening in TheBDM.run()

The daemon still runs and correctly responds to getnewaddress however I haven't got a suitable wallet to test the other calls, namely:
getbalance
getreceivedbyaddress
listtransactions
sendtoaddress

Please see https://github.com/thedawnrider/BitcoinArmory-Daemon/blob/master/armory-daemon.py for the source

Oh boy.  I forgot about this thread, and should've warned you that I converted Armory to a multi-threaded application.  As such, some of the interfaces changed a bit.  There is an example of blockchain loading in the extras/sample_armory_code.py file, but I'm not sure all the samples are up-to-date for the new multi-threaded code.  On the upside, just about any function that doesn't return something (like blockchain scanning) you can use wait=False and it will run in the background allowing you to do other operations while you wait. 

You'll see what I mean if you look at the blocking vs. non-blocking methods, shown in sample_armory_code.py:

Code:
################################################################################
if run_LoadBlockchain_Block:
   start = RightNow()
   TheBDM.setBlocking(True)
   TheBDM.setOnlineMode(True)
   # The setOnlineMode should block until blockchain loading is complete
   print 'Loading blockchain took %0.1f sec' % (RightNow() - start)

   topBlock = TheBDM.getTopBlockHeight()
   print '\n\nCurrent Top Block is:', topBlock
   TheBDM.getTopBlockHeader().pprint()

As above, if you set blocking to True, it should behave much like the original... the main thread always waits for a response from the BDM before continuing.  On the other hand, if blocking=False (default):

Code:
################################################################################
if run_LoadBlockchain_Async:
   start = RightNow()
   TheBDM.setBlocking(False)
   TheBDM.setOnlineMode(True)
   print 'Waiting for blockchain loading to finish',
   while not TheBDM.getBDMState()=='BlockchainReady':
      print '.',
      sleep(2)
      # do other stuff while waiting...
   print 'Loading blockchain took %0.1f sec' % (RightNow() - start)

   topBlock = TheBDM.getTopBlockHeight()
   print '\n\nCurrent Top Block is:', topBlock
   TheBDM.getTopBlockHeader().pprint()

In this case, the blockchain scan happens in the background and the main thread continues running immediately, giving you an opportunity to do other calculations or operations in the main thread.  If you use the setBlocking=False, you should ALWAYS check theBDMState() before making a call like getTopBlockHeader() which you expect to return data -- anything that returns data is blocking by default.  Use a conditional like this:

Code:
if TheBDM.getBDMState()=='BlockchainReady':
   # The blockchain is loaded and TheBDM is sitting idle waiting for something to do
elif TheBDM.getBDMState()=='Scanning':
   # Currently scanning the blockchain, go do something else
elif TheBDM.getBDMState()=='Offline':
   # Blockchain is not loaded and was not requested to be loaded (usually because of --offline)
elif TheBDM.getBDMState()=='Uninitialized':
   # The BDM expects it will be online at some point, but the blockchain isn't loaded yet

"Uninitialized" and "Offline" are pretty much the same, so I usually use "if TheBDM.getBDMState() in ('Uninitialized','Offline'): ..."

Anyways, I'm sure there's other things that are new, but obviously ArmoryQt.py is functioning correctly, so there's lots of sample code in there.  Really, 95% of it is the same, just some new/renamed calls and slightly different arguments.  For this application, you probably want setBlocking(True), since you might just be adding complication to an app that doesn't actually need the multi-threading.  If you run the latest Armory GUI, you'll see why I made the change.  MUCH better user experience...

Sorry about that!  Please pester me about more errors, and I'll help you recover from this under-the-hood overhaul...
(and sorry in advance, there's probably another one coming... upgrading to completely new wallets and unicode support...)
mav
full member
Activity: 169
Merit: 107
I have recently pushed changes to the daemon to bring it up to date with the latest armoryengine.py

Unfortunately I am unable to load the blockchain, and am getting these errors.

Code:
(ERROR) armoryengine.py:11362 - Error processing BDM input
(ERROR) armoryengine.py:11363 - Received inputTuple: StartScanRequested [8, 28474661, False]
(ERROR) armoryengine.py:11364 - Error processing ID (28474661)

Any ideas why these errors are being thrown? This is happening in TheBDM.run()

The daemon still runs and correctly responds to getnewaddress however I haven't got a suitable wallet to test the other calls, namely:
getbalance
getreceivedbyaddress
listtransactions
sendtoaddress

Please see https://github.com/thedawnrider/BitcoinArmory-Daemon/blob/master/armory-daemon.py for the source
legendary
Activity: 1199
Merit: 1012
You are right, there is no alternate chain for change addresses.  It is possible to end up with accidentally re-using an address once, especially if your server is very active in giving out addresses.

However, in version 0.82 I added a new feature to the Expert interface (in the GUI) that lets you specify the change address to use.  It allows you to send change back to the first input address (may be better than the alternative), or you can supply your own address.  You can't specify another wallet yet, but I want to allow that, eventually.

However, the new wallet format that will come after beta, is based on endless discussion with Pieter Wiulle (bitcoin-qt dev), and that will result in wallets, by default, having two different subchains for each wallet -- the second chain will be used solely for change.  The new wallet format is a ways off (hopefully Beta isn't!), but it is coming.  For now, it sounds like switching your desktop to Expert usermode is your solution.



Thanks! Great software!
legendary
Activity: 1428
Merit: 1093
Core Armory Developer
You are right, there is no alternate chain for change addresses.  It is possible to end up with accidentally re-using an address once, especially if your server is very active in giving out addresses.

However, in version 0.82 I added a new feature to the Expert interface (in the GUI) that lets you specify the change address to use.  It allows you to send change back to the first input address (may be better than the alternative), or you can supply your own address.  You can't specify another wallet yet, but I want to allow that, eventually.

However, the new wallet format that will come after beta, is based on endless discussion with Pieter Wiulle (bitcoin-qt dev), and that will result in wallets, by default, having two different subchains for each wallet -- the second chain will be used solely for change.  The new wallet format is a ways off (hopefully Beta isn't!), but it is coming.  For now, it sounds like switching your desktop to Expert usermode is your solution.

legendary
Activity: 1199
Merit: 1012
But would it be safe, or would Armory generate change addresses that may conflict with addresses generated on server?

I made a simple experiment and noticed that change addresses come from the same sequence. Is there a way around this feature?
legendary
Activity: 1199
Merit: 1012
There won't be any fireworks, but you might end up re-using an address by accident.

I am sorry, I am misunderstanding something. I like the idea of deterministic address generation without private keys. But does Armory deterministically separate change addresses from normal ones?

Let's say I don't want to use offline transactions. I just want to generate addresses with watching-only wallet (being run on the web server machine) and then spend the received bitcoins from the full wallet (being run on my desktop computer).

I like this idea since it doesn't require any synchronization between wallets.

But would it be safe, or would Armory generate change addresses that may conflict with addresses generated on server? Is there any chance to make this simple scheme work?
hero member
Activity: 868
Merit: 1000
Anyway I'll keep updating the code if I find so way to improve it with. But for now I have to focus on a way to generate an offline TX and push it to an offsite computer to be signed and broadcast immediately. Because if you don't broadcast your unsigned tx immediately, the next generated one should be incorrect :/

This is an interesting point you make. On one hand it seems fairly certain you are right about this, but maybe etotheipi can confirm whether the wallet can handle the creation of multiple unsigned transactions at a time? It seems like a fairly likely situation to happen under most normal use cases, even with the gui client. If the daemon software can avoid having to implement a strict 'sequential' series of transaction where the server must create-a-tx-then-announce-before-creating-the-next-tx, it would make life much much easier.

TBH I always assumed transactions could be grouped and processed offline as a batch. If armory does require one-at-a-time, that would become a very limiting factor for even a moderately popular web-based service.

I tested several send transactions in a row (with same parameters), and for each transactions the inputs and outputs are the same. Which is logical since armory look at the existing adresses balances to construct a transaction, if a transaction is not broadcasted it can't be taken into account.

I don't know if etotheipi is gonna change this behavior in the future, but for now you have to broadcast an unsigned transaction before creating the other one, no other way around it.

What I'm thinking to get around this limitation is to have a computer, completely behind a firewall, which pull from production servers all unsigned txs when they are created, signed and broadcast them. Then it send a signal to the productions servers (delete the unsigned tx file?) to go ahead and create the next unsigned tx if needed.

That's not as secure as offline signing, especially if signing is automated, but I think it's still relatively secure compared to existing solution with bitcoind.
mav
full member
Activity: 169
Merit: 107
Anyway I'll keep updating the code if I find so way to improve it with. But for now I have to focus on a way to generate an offline TX and push it to an offsite computer to be signed and broadcast immediately. Because if you don't broadcast your unsigned tx immediately, the next generated one should be incorrect :/

This is an interesting point you make. On one hand it seems fairly certain you are right about this, but maybe etotheipi can confirm whether the wallet can handle the creation of multiple unsigned transactions at a time? It seems like a fairly likely situation to happen under most normal use cases, even with the gui client. If the daemon software can avoid having to implement a strict 'sequential' series of transaction where the server must create-a-tx-then-announce-before-creating-the-next-tx, it would make life much much easier.

TBH I always assumed transactions could be grouped and processed offline as a batch. If armory does require one-at-a-time, that would become a very limiting factor for even a moderately popular web-based service.
hero member
Activity: 868
Merit: 1000
Ok so I updated the previous code which didn't work at all.
...

Massive kudos for making this happen unclescrooge. Armory takes a fair bit of poking around, so it seems you've done well with this.

I added the code and have written some basic tests, unfortunately time is unexpectedly short for me this weekend so later in the week I will try adding the missing fields from listtransactions (and properly confirm that the existing fields are correct).

See the latest on github for the changes. I appreciate the efforts you have put in unclescrooge. It's nice to know people are looking at this, gives me motivation to continue on it. I was planning to use this in a project I was working on, but that has been put on the backburner and as such armory-daemon has languished.

Well thank you, you did the most part, and I had an interest in this as I'm launching a new bitcoin service using this script.

I hope you tested it before pushing to git, I did but the more testing the better. Thanks for pushing it to github anyway Smiley

Anyway I'll keep updating the code if I find so way to improve it with. But for now I have to focus on a way to generate an offline TX and push it to an offsite computer to be signed and broadcast immediately. Because if you don't broadcast your unsigned tx immediately, the next generated one should be incorrect :/
mav
full member
Activity: 169
Merit: 107
Ok so I updated the previous code which didn't work at all.
...

Massive kudos for making this happen unclescrooge. Armory takes a fair bit of poking around, so it seems you've done well with this.

I added the code and have written some basic tests, unfortunately time is unexpectedly short for me this weekend so later in the week I will try adding the missing fields from listtransactions (and properly confirm that the existing fields are correct).

See the latest on github for the changes. I appreciate the efforts you have put in unclescrooge. It's nice to know people are looking at this, gives me motivation to continue on it. I was planning to use this in a project I was working on, but that has been put on the backburner and as such armory-daemon has languished.
hero member
Activity: 868
Merit: 1000
Ok so I updated the previous code which didn't work at all.

Now I manage to get working  "getbalance()", "listtransactions()" (only a few items that I think important are returned though), "sendtoaddress()" (create a file containing the unsigned transaction in the current directory and return a random string).

Do not use in production server without testing though:

Code:
################################################################################
#
# Copyright (C) 2012, Ian Coleman
# Distributed under the GNU Affero General Public License (AGPL v3)
# See http://www.gnu.org/licenses/agpl.html
#
################################################################################

from twisted.web import server
from twisted.internet import reactor
from txjsonrpc.web import jsonrpc

import decimal
import os

RPC_PORT = 7070
STANDARD_FEE = 0.0005 # BTC
       
class Wallet_Json_Rpc_Server(jsonrpc.JSONRPC):

    def __init__(self, wallet):
        self.wallet = wallet

    def jsonrpc_getnewaddress(self):
        addr = self.wallet.getNextUnusedAddress()
        return addr.getAddrStr()

    def jsonrpc_getbalance(self):
        int_balance = self.wallet.getBalance()
        decimal_balance = decimal.Decimal(int_balance) / decimal.Decimal(ONE_BTC)
        return float(decimal_balance)

    def jsonrpc_getreceivedbyaddress(self, address):
        if CLI_OPTIONS.offline:
            raise ValueError('Cannot get received amount when offline')
        # Only gets correct amount for addresses in the wallet, otherwise 0
        addr160 = addrStr_to_hash160(address)
        txs = self.wallet.getAddrTxLedger(addr160)
        balance = sum([x.getValue() for x in txs if x.getValue() > 0])
        decimal_balance = decimal.Decimal(balance) / decimal.Decimal(ONE_BTC)
        float_balance = float(decimal_balance)
        return float_balance

    def jsonrpc_sendtoaddress(self, bitcoinaddress, amount):
        if CLI_OPTIONS.offline:
            raise ValueError('Cannot create transactions when offline')
        return self.create_unsigned_transaction(bitcoinaddress, amount)

    def jsonrpc_listtransactions(self, p_count=10, p_from=0):
        #TODO this needs more work
        result2 = []
        txs = self.wallet.getTxLedger('blk')
        #txs = json.dumps(txs)
        #id_le_pairs = [[le] for le in txs]
        for x in txs:
            #result.append('{')
            account = ''
            txHashBin = x.getTxHash()#hex_to_binary(x.getTxHash())   
            cppTx = TheBDM.getTxByHash(txHashBin)
            pytx = PyTx().unserialize(cppTx.serialize())
            for txout in pytx.outputs:
                scrType = getTxOutScriptType(txout.binScript)
                if not scrType in (TXOUT_SCRIPT_STANDARD, TXOUT_SCRIPT_COINBASE):
                  continue
                address = hash160_to_addrStr(TxOutScriptExtractAddr160(txout.binScript))
                #wltID = self.wallet.getWalletForAddr160(a160)
                if self.wallet.hasAddr(address) == False:
                  continue
                else:
                break
            if x.getValue() < 0:
            category = 'send'
            else:
            category = 'receive'
            amount = float(decimal.Decimal(x.getValue()) / decimal.Decimal(ONE_BTC))
            confirmations = TheBDM.getTopBlockHeader().getBlockHeight() - x.getBlockNum()+1
            blockhash = ''
            blockindex = ''#x.getBlockNum()
            txid = str(binary_to_hex(x.getTxHash()))
            time = ''#x.getTxTime()
            result = {'account':account,'address':address,'category':category,'amount':amount,'confirmations':confirmations,'blockhash':blockhash,'blockindex':blockindex,'txid':txid,'time:':time}
            #result.append('}')
            result2.append(result)
            #print addr
            #dumpObj(x)   
        #result = json.dumps(result)
        return result2

       
    # https://bitcointalk.org/index.php?topic=92496.msg1126310#msg1126310
    def create_unsigned_transaction(self, bitcoinaddress_str, amount_to_send_btc):
        # Get unspent TxOutList and select the coins
        addr160_recipient = addrStr_to_hash160(bitcoinaddress_str)
        totalSend, fee = long(amount_to_send_btc * ONE_BTC), (STANDARD_FEE * ONE_BTC)
        spendBal = self.wallet.getBalance('Spendable')
        utxoList = self.wallet.getTxOutList('Spendable')
        utxoSelect = PySelectCoins(utxoList, totalSend, fee)

        minFeeRec = calcMinSuggestedFees(utxoSelect, totalSend, fee)[1]
        if fee            if totalSend + minFeeRec > spendBal:
                raise NotEnoughCoinsError, "You can't afford the fee!"
            utxoSelect = PySelectCoins(utxoList, totalSend, minFeeRec)
            fee = minFeeRec

        if len(utxoSelect)==0:
            raise CoinSelectError, "Somehow, coin selection failed.  This shouldn't happen"

        totalSelected = sum([u.getValue() for u in utxoSelect])
        totalChange = totalSelected - (totalSend  + fee)

        outputPairs = []
        outputPairs.append( [addr160_recipient, totalSend] )
        if totalChange > 0:
            outputPairs.append( [self.wallet.getNextUnusedAddress().getAddr160(), totalChange] )

        random.shuffle(outputPairs)
        txdp = PyTxDistProposal().createFromTxOutSelection(utxoSelect, outputPairs)
        outfilename = str(int(time.time())) + '.unsigned.tx'
        output = open(outfilename, 'w')
        output.write(txdp.serializeAscii())
        output.close()
        result2 = []
        result = {"Code":""}
        result2.append(result)     
        return "success"+str(int(time.time()))
       
    def jsonrpc_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()


      self.combinedLedger = []
      totalFunds  = 0
      spendFunds  = 0
      unconfFunds = 0
      currBlk = 0xffffffff
      if TheBDM.isInitialized():
         currBlk = TheBDM.getTopBlockHeader().getBlockHeight()

      wlt = self.waldlet
      totalFunds += wlt.getBalance('Total')
      spendFunds += wlt.getBalance('Spendable')
      unconfFunds += wlt.getBalance('Unconfirmed')


      self.combinedLedger.sort(key=lambda x: x[LEDGERCOLS.UnixTime], reverse=True)
      self.ledgerSize = len(self.combinedLedger)
     
      print totalFunds
      return

class Armory_Daemon():

    def __init__(self):

        print "Reading wallet file"
        self.wallet = self.find_wallet()
        self.loadBlockchain()
       
        use_blockchain = not CLI_OPTIONS.offline
       
        print "Initialising server"
        reactor.listenTCP(RPC_PORT, server.Site(Wallet_Json_Rpc_Server(self.wallet)))

        self.NetworkingFactory = ArmoryClientFactory( \
                                func_loseConnect=self.showOfflineMsg, \
                                func_madeConnect=self.showOnlineMsg, \
                                func_newTx=self.newTxFunc)
                               
        reactor.connectTCP('127.0.0.1', BITCOIN_PORT, self.NetworkingFactory)
        reactor.callLater(5, self.Heartbeat)
        self.start()

    def start(self):
        print "Server started"
        reactor.run()
       
    def newTxFunc(self, pytxObj):
        # Cut down version from ArmoryQt.py
        TheBDM.addNewZeroConfTx(pytxObj.serialize(), long(RightNow()), True)
        TheBDM.rescanWalletZeroConf(self.wallet.cppWallet)

        # TODO set up a 'subscribe' feature so these notifications can be
        # pushed out to interested parties.

        # From here down is display purposes only, copied from ArmoryQt.py
        message = "New TX"
        le = self.wallet.cppWallet.calcLedgerEntryForTxStr(pytxObj.serialize())
        if not le.isSentToSelf():
            txref = TheBDM.getTxByHash(le.getTxHash())
            nOut = txref.getNumTxOut()
            recips = [txref.getTxOut(i).getRecipientAddr() for i in range(nOut)]
            values = [txref.getTxOut(i).getValue()         for i in range(nOut)]
            idxMine  = filter(lambda i:     self.wallet.hasAddr(recips[i]), range(nOut))
            idxOther = filter(lambda i: not self.wallet.hasAddr(recips[i]), range(nOut))
            mine  = [(recips[i],values[i]) for i in idxMine]
            other = [(recips[i],values[i]) for i in idxOther]

            # Collected everything we need to display, now construct it and do it
            if le.getValue()>0:
               # Received!
               message = 'Bitcoins Received!'
               totalStr = coin2str( sum([mine[i][1] for i in range(len(mine))]), maxZeros=1)
               message += '\nAmount: \t%s BTC' % totalStr.strip()
               if len(mine)==1:
                  message += '\nAddress:\t%s' % hash160_to_addrStr(mine[0][0])
                  addrComment = self.wallet.getComment(mine[0][0])
                  #if addrComment:
                     #message += '\n%s...' % addrComment[:24]
               else:
                  message += '\n'
            elif le.getValue()<0:
               # Sent!
               message = 'Bitcoins Sent!'
               totalStr = coin2str( sum([other[i][1] for i in range(len(other))]), maxZeros=1)
               message += '\nAmount: \t%s BTC' % totalStr.strip()
               if len(other)==1:
                  message += 'Sent To:\t%s' % hash160_to_addrStr(other[0][0])
                  addrComment = self.wallet.getComment(other[0][0])
                  #if addrComment:
                     #message += '\n%s...' % addrComment[:24]
               else:
                  dispLines.append('')
        else:
            amt = self.determineSentToSelfAmt(le, self.wallet)[0]
            message = 'Wallet "%s" just sent %s BTC to itself!' % \
               (self.wallet.labelName, coin2str(amt,maxZeros=1).strip())
               
        print message

    def determineSentToSelfAmt(self, le, wlt):
      """
      NOTE:  this method works ONLY because we always generate a new address
             whenever creating a change-output, which means it must have a
             higher chainIndex than all other addresses.  If you did something
             creative with this tx, this may not actually work.
      """
      amt = 0
      if TheBDM.isInitialized() and le.isSentToSelf():
         txref = TheBDM.getTxByHash(le.getTxHash())
         if not txref.isInitialized():
            return (0, 0)
         if txref.getNumTxOut()==1:
            return (txref.getTxOut(0).getValue(), -1)
         maxChainIndex = -5
         txOutChangeVal = 0
         txOutIndex = -1
         valSum = 0
         for i in range(txref.getNumTxOut()):
            valSum += txref.getTxOut(i).getValue()
            addr160 = txref.getTxOut(i).getRecipientAddr()
            addr    = wlt.getAddrByHash160(addr160)
            if addr and addr.chainIndex > maxChainIndex:
               maxChainIndex = addr.chainIndex
               txOutChangeVal = txref.getTxOut(i).getValue()
               txOutIndex = i

         amt = valSum - txOutChangeVal
      return (amt, txOutIndex)

    def showOfflineMsg(self):
        print "Offline - not tracking blockchain"

    def showOnlineMsg(self):
        print "Online - tracking blockchain"

    def find_wallet(self):
        fnames = os.listdir(os.getcwd())
        for fname in fnames:
            is_wallet = fname[-7:] == ".wallet"
            is_watchonly = fname.find("watchonly") > -1
            is_backup = fname.find("backup") > -1
            if(is_wallet and is_watchonly and not is_backup):
                wallet = PyBtcWallet().readWalletFile(fname)
                # Register all wallets with TheBDM
                TheBDM.registerWallet( wallet.cppWallet )
                print "Using wallet file %s" % fname
                return wallet
        raise ValueError('Unable to locate a watch-only wallet in %s' % os.getcwd())
           
    def loadBlockchain(self):

      print "Loading blockchain"
      BDM_LoadBlockchainFile()
      self.latestBlockNum = TheBDM.getTopBlockHeader().getBlockHeight()
   
      # Now that theb blockchain is loaded, let's populate the wallet info
      if TheBDM.isInitialized():
         mempoolfile = os.path.join(ARMORY_HOME_DIR,'mempool.bin')
         self.checkMemoryPoolCorruption(mempoolfile)
         TheBDM.enableZeroConf(mempoolfile)
   
        # self.statusBar().showMessage('Syncing wallets with blockchain...')
         print "Syncing wallets with blockchain..."
         print "Syncing wallet: ", self.wallet.uniqueIDB58
         self.wallet.setBlockchainSyncFlag(BLOCKCHAIN_READONLY)
         self.wallet.syncWithBlockchain()
             
               
         #self.createCombinedLedger()
         #self.ledgerSize = len(self.combinedLedger)
         #self.statusBar().showMessage('Blockchain loaded, wallets sync\'d!', 10000)

    def checkMemoryPoolCorruption(self, mempoolname):
      if not os.path.exists(mempoolname):
         return

      memfile = open(mempoolname, 'r')
      memdata = memfile.read()
      memfile.close()

      binunpacker = BinaryUnpacker(memdata)
      try:
         while binunpacker.getRemainingSize() > 0:
            binunpacker.get(UINT64)
            PyTx().unserialize(binunpacker)
      except:
         os.remove(mempoolname);
         LOGWARN('Memory pool file was corrupt.  Deleted. (no further action is needed)') 
               
    def Heartbeat(self, nextBeatSec=2):
       """
       This method is invoked when the app is initialized, and will
       run every 2 seconds, or whatever is specified in the nextBeatSec
       argument.
       """
       # Check for new blocks in the blk0001.dat file
       if TheBDM.isInitialized():
          newBlks = TheBDM.readBlkFileUpdate()
          self.topTimestamp   = TheBDM.getTopBlockHeader().getTimestamp()
          if newBlks>0:
             self.latestBlockNum = TheBDM.getTopBlockHeader().getBlockHeight()
             didAffectUs = False
             prevLedgerSize = len(self.wallet.getTxLedger())
             self.wallet.syncWithBlockchain()
             TheBDM.rescanWalletZeroConf(self.wallet.cppWallet)

       self.wallet.checkWalletLockTimeout()

       reactor.callLater(nextBeatSec, self.Heartbeat)
 


if __name__ == "__main__":
    from armoryengine import *
    rpc_server = Armory_Daemon()
Pages:
Jump to: