def CheckAndSend_Funded(transactionType, outputs, outputPubKeys, details): TransactionEncoding.FromStateTransaction( transactionType, [], outputs, outputPubKeys, details) # for initial parameter checking transactionBuildLayer.startTransactionConstruction() swapBillUnspent = transactionBuildLayer.getSwapBillUnspent(state) sourceAccounts = [] while True: try: state.checkTransaction(transactionType, outputs=outputs, transactionDetails=details, sourceAccounts=sourceAccounts) except InsufficientFundsForTransaction: pass except TransactionFailsAgainstCurrentState as e: raise TransactionNotSuccessfulAgainstCurrentState( 'Transaction would not complete successfully against current state: ' + str(e)) except BadlyFormedTransaction as e: raise ExceptionReportedToUser( 'Transaction does not meet protocol constraints: ' + str(e)) else: break if not swapBillUnspent: raise ExceptionReportedToUser( 'Insufficient swapbill for transaction.') transactionBuildLayer.swapBillUnspentUsed(swapBillUnspent[0]) sourceAccounts.append(swapBillUnspent[0]) swapBillUnspent = swapBillUnspent[1:] return CheckAndSend_Common(transactionType, sourceAccounts, outputs, outputPubKeys, details)
def signAndSend(self, unsignedTransactionHex, privateKeys, maximumSignedSize): #print('\t\tunsignedTransactionHex =', unsignedTransactionHex.__repr__()) #print('\t\tprivateKeys =', privateKeys.__repr__()) #print('\t\tmaximumSignedSize =', maximumSignedSize.__repr__()) #return None ## lowest level transaction send interface signingResult = self._rpcHost.call('signrawtransaction', unsignedTransactionHex) if signingResult['complete'] != True: privateKeys_WIF = [] for privateKey in privateKeys: privateKeys_WIF.append(Address.PrivateKeyToWIF(privateKey, self._privateKeyAddressVersion)) signingResult = self._rpcHost.call('signrawtransaction', signingResult['hex'], None, privateKeys_WIF) if signingResult['complete'] != True: raise SigningFailed("RPC call to signrawtransaction did not set 'complete' to True") signedHex = signingResult['hex'] byteSize = len(signedHex) / 2 if byteSize > maximumSignedSize: raise MaximumSignedSizeExceeded() try: txID = self._rpcHost.call('sendrawtransaction', signedHex) except RPC.RPCFailureException as e: raise ExceptionReportedToUser('RPC error sending signed transaction: ' + str(e)) with open(self._submittedTransactionsFileName, mode='a') as f: f.write(txID) f.write('\n') f.write(signedHex) f.write('\n') return txID
def PercentFromString(s): result = _fromString(s, _percentDigits) if result == 0 or result >= percentDivisor: raise ExceptionReportedToUser( 'Bad percentage string (value must be greater than 0.0 and less than 1.0).' ) return result
def _fromString(s, decimalDigits): if s[0] == '-': raise ExceptionReportedToUser( 'Bad decimal string (negative values are not permitted).') pos = s.find('.') if pos == -1: integerString = s + '0' * decimalDigits else: digitsAfter = len(s) - 1 - pos if digitsAfter > decimalDigits: raise ExceptionReportedToUser( 'Too much precision in decimal string (a maximum of {} digits are allowed after the decimal point).' .format(decimalDigits)) digitsToAdd = decimalDigits - digitsAfter integerString = s[:pos] + s[pos + 1:] + '0' * digitsToAdd return int(integerString)
def getBlockHashAtIndexOrNone(self, blockIndex): try: return self._rpcHost.call('getblockhash', blockIndex) except RPC.RPCFailureWithMessage as e: if str(e) == 'Block number out of range.': return None except RPC.RPCFailureException: pass raise ExceptionReportedToUser('Unexpected RPC error in call to getblockhash.')
def CheckAndSend_UnFunded(transactionType, outputs, outputPubKeys, details): TransactionEncoding.FromStateTransaction( transactionType, None, outputs, outputPubKeys, details) # for initial parameter checking transactionBuildLayer.startTransactionConstruction() try: state.checkTransaction(transactionType, outputs=outputs, transactionDetails=details, sourceAccounts=None) except TransactionFailsAgainstCurrentState as e: raise TransactionNotSuccessfulAgainstCurrentState( 'Transaction would not complete successfully against current state: ' + str(e)) except BadlyFormedTransaction as e: raise ExceptionReportedToUser( 'Transaction does not follow protocol rules: ' + str(e)) return CheckAndSend_Common(transactionType, None, outputs, outputPubKeys, details)
def _consumeUnspent(self, txID, vOut): unspentAfter = [] found = None for entry in self._unspent: if entry['txid'] == txID and entry['vout'] == vOut: assert found is None found = entry else: unspentAfter.append(entry) if found is None: raise ExceptionReportedToUser( 'RPC error sending signed transaction: (from Mock Host, no unspent found for input, maybe already spent?)' ) pubKeyHash = found['address'] if self._isHostAddress(pubKeyHash): pubKeyHashToBeSigned = None else: pubKeyHashToBeSigned = pubKeyHash assert pubKeyHashToBeSigned is not None self._unspent = unspentAfter return found['amount'], pubKeyHashToBeSigned
def Main(startBlockIndex, startBlockHash, useTestNet, commandLineArgs=sys.argv[1:], host=None, keyGenerator=None, out=sys.stdout): args = parser.parse_args(commandLineArgs) if not path.isdir(args.dataDir): raise ExceptionReportedToUser( "The following path (specified for data directory parameter) is not a valid path to an existing directory: " + args.dataDir) dataDir = path.join(args.dataDir, 'swapBillData') if not path.exists(dataDir): try: os.mkdir(dataDir) except Exception as e: raise ExceptionReportedToUser( "Failed to create directory " + dataDir + ":", e) if useTestNet: addressVersion = b'\x6f' privateKeyAddressVersion = b'\xef' else: addressVersion = b'\x30' privateKeyAddressVersion = b'\xbf' wallet = Wallet.Wallet(path.join(dataDir, 'wallet.txt'), privateKeyAddressVersion=privateKeyAddressVersion, keyGenerator=keyGenerator ) # litecoin testnet private key address version if host is None: configFile = args.configFile if configFile is None: if os.name == 'nt': configFile = path.join(path.expanduser("~"), 'AppData', 'Roaming', 'Litecoin', 'litecoin.conf') else: configFile = path.join(path.expanduser("~"), '.litecoin', 'litecoin.conf') with open(configFile, mode='rb') as f: configFileBuffer = f.read() clientConfig = ParseConfig.Parse(configFileBuffer) RPC_HOST = clientConfig.get('externalip', 'localhost') try: RPC_PORT = clientConfig['rpcport'] except KeyError: if useTestNet: RPC_PORT = 19332 else: RPC_PORT = 9332 assert int(RPC_PORT) > 1 and int(RPC_PORT) < 65535 try: RPC_USER = clientConfig['rpcuser'] RPC_PASSWORD = clientConfig['rpcpassword'] except KeyError: raise ExceptionReportedToUser( 'Values for rpcuser and rpcpassword must both be set in your config file.' ) rpcHost = RPC.Host('http://' + RPC_USER + ':' + RPC_PASSWORD + '@' + RPC_HOST + ':' + str(RPC_PORT)) submittedTransactionsLogFileName = path.join( dataDir, 'submittedTransactions.txt') host = Host.Host( rpcHost=rpcHost, addressVersion=addressVersion, privateKeyAddressVersion=privateKeyAddressVersion, submittedTransactionsLogFileName=submittedTransactionsLogFileName) includePending = hasattr(args, 'includepending') and args.includepending if args.action == 'get_state_info': syncOut = io.StringIO() startTime = time.clock() state, ownedAccounts = SyncAndReturnStateAndOwnedAccounts( dataDir, startBlockIndex, startBlockHash, wallet, host, includePending=includePending, forceRescan=args.forceRescan, out=syncOut) elapsedTime = time.clock() - startTime formattedBalances = {} for account in state._balances.balances: key = host.formatAccountForEndUser(account) formattedBalances[key] = state._balances.balanceFor(account) info = { 'totalCreated': state._totalCreated, 'atEndOfBlock': state._currentBlockIndex - 1, 'balances': formattedBalances, 'syncOutput': syncOut.getvalue(), 'syncTime': elapsedTime, 'numberOfLTCBuyOffers': state._ltcBuys.size(), 'numberOfLTCSellOffers': state._ltcSells.size(), 'numberOfPendingExchanges': len(state._pendingExchanges), 'numberOfOutputs': len(ownedAccounts.accounts) } return info state, ownedAccounts = SyncAndReturnStateAndOwnedAccounts( dataDir, startBlockIndex, startBlockHash, wallet, host, includePending=includePending, forceRescan=args.forceRescan, out=out) transactionBuildLayer = TransactionBuildLayer.TransactionBuildLayer( host, ownedAccounts) def SetFeeAndSend(baseTX, baseTXInputsAmount, unspent): change = host.getNewNonSwapBillAddress() maximumSignedSize = TransactionFee.startingMaximumSize transactionFee = TransactionFee.startingFee while True: try: filledOutTX = BuildHostedTransaction.AddPaymentFeesAndChange( baseTX, baseTXInputsAmount, TransactionFee.dustLimit, transactionFee, unspent, change) return transactionBuildLayer.sendTransaction( filledOutTX, maximumSignedSize) except Host.MaximumSignedSizeExceeded: print("Transaction fee increased.", file=out) maximumSignedSize += TransactionFee.sizeStep transactionFee += TransactionFee.feeStep def CheckAndSend_Common(transactionType, sourceAccounts, outputs, outputPubKeys, details): change = host.getNewNonSwapBillAddress() print('attempting to send ' + FormatTransactionForUserDisplay.Format( host, transactionType, outputs, outputPubKeys, details), file=out) baseTX = TransactionEncoding.FromStateTransaction( transactionType, sourceAccounts, outputs, outputPubKeys, details) backingUnspent = transactionBuildLayer.getUnspent() baseInputsAmount = 0 for i in range(baseTX.numberOfInputs()): txID = baseTX.inputTXID(i) vOut = baseTX.inputVOut(i) baseInputsAmount += ownedAccounts.accounts[(txID, vOut)][0] txID = SetFeeAndSend(baseTX, baseInputsAmount, backingUnspent) return {'transaction id': txID} def CheckAndSend_Funded(transactionType, outputs, outputPubKeys, details): TransactionEncoding.FromStateTransaction( transactionType, [], outputs, outputPubKeys, details) # for initial parameter checking transactionBuildLayer.startTransactionConstruction() swapBillUnspent = transactionBuildLayer.getSwapBillUnspent(state) sourceAccounts = [] while True: try: state.checkTransaction(transactionType, outputs=outputs, transactionDetails=details, sourceAccounts=sourceAccounts) except InsufficientFundsForTransaction: pass except TransactionFailsAgainstCurrentState as e: raise TransactionNotSuccessfulAgainstCurrentState( 'Transaction would not complete successfully against current state: ' + str(e)) except BadlyFormedTransaction as e: raise ExceptionReportedToUser( 'Transaction does not meet protocol constraints: ' + str(e)) else: break if not swapBillUnspent: raise ExceptionReportedToUser( 'Insufficient swapbill for transaction.') transactionBuildLayer.swapBillUnspentUsed(swapBillUnspent[0]) sourceAccounts.append(swapBillUnspent[0]) swapBillUnspent = swapBillUnspent[1:] return CheckAndSend_Common(transactionType, sourceAccounts, outputs, outputPubKeys, details) def CheckAndSend_UnFunded(transactionType, outputs, outputPubKeys, details): TransactionEncoding.FromStateTransaction( transactionType, None, outputs, outputPubKeys, details) # for initial parameter checking transactionBuildLayer.startTransactionConstruction() try: state.checkTransaction(transactionType, outputs=outputs, transactionDetails=details, sourceAccounts=None) except TransactionFailsAgainstCurrentState as e: raise TransactionNotSuccessfulAgainstCurrentState( 'Transaction would not complete successfully against current state: ' + str(e)) except BadlyFormedTransaction as e: raise ExceptionReportedToUser( 'Transaction does not follow protocol rules: ' + str(e)) return CheckAndSend_Common(transactionType, None, outputs, outputPubKeys, details) def CheckAndReturnPubKeyHash(address): try: pubKeyHash = host.addressFromEndUserFormat(address) except Address.BadAddress as e: raise BadAddressArgument(address) return pubKeyHash if args.action == 'burn': amount = Amounts.FromString(args.amount) if amount < TransactionFee.dustLimit: raise ExceptionReportedToUser('Burn amount is below dust limit.') transactionType = 'Burn' outputs = ('destination', ) outputPubKeyHashes = (wallet.addKeyPairAndReturnPubKeyHash(), ) details = {'amount': amount} return CheckAndSend_Funded(transactionType, outputs, outputPubKeyHashes, details) elif args.action == 'pay': transactionType = 'Pay' outputs = ('change', 'destination') outputPubKeyHashes = (wallet.addKeyPairAndReturnPubKeyHash(), CheckAndReturnPubKeyHash(args.toAddress)) details = { 'amount': Amounts.FromString(args.amount), 'maxBlock': state._currentBlockIndex + args.blocksUntilExpiry } return CheckAndSend_Funded(transactionType, outputs, outputPubKeyHashes, details) elif args.action == 'post_ltc_buy': transactionType = 'LTCBuyOffer' outputs = ('ltcBuy', ) outputPubKeyHashes = (wallet.addKeyPairAndReturnPubKeyHash(), ) details = { 'swapBillOffered': Amounts.FromString(args.swapBillOffered), 'exchangeRate': Amounts.PercentFromString(args.exchangeRate), 'receivingAddress': host.getNewNonSwapBillAddress(), 'maxBlock': state._currentBlockIndex + args.blocksUntilExpiry } return CheckAndSend_Funded(transactionType, outputs, outputPubKeyHashes, details) elif args.action == 'post_ltc_sell': details = { 'exchangeRate': Amounts.PercentFromString(args.exchangeRate) } if args.backerID is None: transactionType = 'LTCSellOffer' outputs = ('ltcSell', ) details[ 'maxBlock'] = state._currentBlockIndex + args.blocksUntilExpiry details['ltcOffered'] = Amounts.FromString(args.ltcOffered) else: backerID = int(args.backerID) if not backerID in state._ltcSellBackers: raise ExceptionReportedToUser( 'No backer with the specified ID.') backer = state._ltcSellBackers[backerID] transactionType = 'BackedLTCSellOffer' outputs = ('sellerReceive', ) ltc = Amounts.FromString(args.ltcOffered) if args.includesCommission: details['ltcOfferedPlusCommission'] = ltc else: ltcCommission = ltc * backer.commission // Amounts.percentDivisor details['ltcOfferedPlusCommission'] = ltc + ltcCommission details['backerIndex'] = int(args.backerID) details['backerLTCReceiveAddress'] = backer.ltcReceiveAddress outputPubKeyHashes = (wallet.addKeyPairAndReturnPubKeyHash(), ) return CheckAndSend_Funded(transactionType, outputs, outputPubKeyHashes, details) elif args.action == 'complete_ltc_sell': transactionType = 'LTCExchangeCompletion' pendingExchangeID = int(args.pendingExchangeID) if not pendingExchangeID in state._pendingExchanges: raise ExceptionReportedToUser( 'No pending exchange with the specified ID.') exchange = state._pendingExchanges[pendingExchangeID] details = { 'pendingExchangeIndex': pendingExchangeID, 'destinationAddress': exchange.buyerLTCReceive, 'destinationAmount': exchange.ltc } return CheckAndSend_UnFunded(transactionType, (), (), details) elif args.action == 'back_ltc_sells': transactionType = 'BackLTCSells' outputs = ('ltcSellBacker', ) outputPubKeyHashes = (wallet.addKeyPairAndReturnPubKeyHash(), ) details = { 'backingAmount': Amounts.FromString(args.backingSwapBill), 'transactionsBacked': int(args.transactionsBacked), 'ltcReceiveAddress': host.getNewNonSwapBillAddress(), 'commission': Amounts.PercentFromString(args.commission), 'maxBlock': state._currentBlockIndex + args.blocksUntilExpiry } return CheckAndSend_Funded(transactionType, outputs, outputPubKeyHashes, details) elif args.action == 'get_receive_address': pubKeyHash = wallet.addKeyPairAndReturnPubKeyHash() return {'receive_address': host.formatAddressForEndUser(pubKeyHash)} elif args.action == 'get_balance': total = 0 for account in ownedAccounts.accounts: total += state._balances.balanceFor(account) return {'balance': Amounts.ToString(total)} elif args.action == 'get_buy_offers': result = [] for offer in state._ltcBuys.getSortedOffers(): mine = offer.refundAccount in ownedAccounts.tradeOfferChangeCounts exchangeAmount = offer._swapBillOffered ltc = offer.ltcEquivalent() details = { 'swapbill offered': Amounts.ToString(exchangeAmount), 'ltc equivalent': Amounts.ToString(ltc), 'mine': mine } result.append(('exchange rate', Amounts.PercentToString(offer.rate), details)) return result elif args.action == 'get_sell_offers': result = [] for offer in state._ltcSells.getSortedOffers(): mine = offer.receivingAccount in ownedAccounts.tradeOfferChangeCounts ltc = offer._ltcOffered depositAmount = offer._swapBillDeposit swapBillEquivalent = offer.swapBillEquivalent() details = { 'ltc offered': Amounts.ToString(ltc), 'deposit': Amounts.ToString(depositAmount), 'swapbill equivalent': Amounts.ToString(swapBillEquivalent), 'mine': mine } if offer.isBacked: details['backer id'] = offer.backerIndex result.append(('exchange rate', Amounts.PercentToString(offer.rate), details)) return result elif args.action == 'get_pending_exchanges': result = [] for key in state._pendingExchanges: d = {} exchange = state._pendingExchanges[key] d['I am seller (and need to complete)'] = exchange.sellerAccount in ownedAccounts.tradeOfferChangeCounts d['I am buyer (and waiting for payment)'] = exchange.buyerAccount in ownedAccounts.tradeOfferChangeCounts d['deposit paid by seller'] = Amounts.ToString( exchange.swapBillDeposit) d['swap bill paid by buyer'] = Amounts.ToString( exchange.swapBillAmount) d['outstanding ltc payment amount'] = Amounts.ToString( exchange.ltc) d['expires on block'] = exchange.expiry d['blocks until expiry'] = exchange.expiry - state._currentBlockIndex + 1 if exchange.backerIndex != -1: d['backer id'] = exchange.backerIndex result.append(('pending exchange index', key, d)) return result elif args.action == 'get_ltc_sell_backers': result = [] for key in state._ltcSellBackers: d = {} backer = state._ltcSellBackers[key] d['I am backer'] = backer.refundAccount in ownedAccounts.tradeOfferChangeCounts d['backing amount'] = Amounts.ToString(backer.backingAmount) d['maximum per transaction'] = Amounts.ToString( backer.transactionMax) d['expires on block'] = backer.expiry d['blocks until expiry'] = backer.expiry - state._currentBlockIndex + 1 d['commission'] = Amounts.PercentToString(backer.commission) result.append(('ltc sell backer index', key, d)) return result else: parser.print_help()
def SyncAndReturnStateAndOwnedAccounts(cacheDirectory, startBlockIndex, startBlockHash, wallet, host, includePending, forceRescan, out): loaded = False if not forceRescan: try: (blockIndex, blockHash, state) = PickledCache.Load(cacheDirectory, 'State', stateVersion) ownedAccounts = PickledCache.Load(cacheDirectory, 'OwnedAccounts', ownedAccountsVersion) loaded = True except PickledCache.LoadFailedException as e: print( 'Failed to load from cache, full index generation required (' + str(e) + ')', file=out) if loaded and host.getBlockHashAtIndexOrNone(blockIndex) != blockHash: print( 'The block corresponding with cached state has been orphaned, full index generation required.', file=out) loaded = False if loaded and not state.startBlockMatches(startBlockHash): print( 'Start config does not match config from loaded state, full index generation required.', file=out) loaded = False if loaded: print('Loaded cached state data successfully', file=out) else: blockIndex = startBlockIndex blockHash = host.getBlockHashAtIndexOrNone(blockIndex) if blockHash is None: raise ExceptionReportedToUser( 'Block chain has not reached the swapbill start block (' + str(startBlockIndex) + ').') if blockHash != startBlockHash: raise ExceptionReportedToUser( 'Block hash for swapbill start block does not match.') state = State.State(blockIndex, blockHash) ownedAccounts = OwnedAccounts.OwnedAccounts() print('State update starting from block', blockIndex, file=out) toProcess = deque() mostRecentHash = blockHash while True: nextBlockHash = host.getNextBlockHash(mostRecentHash) if nextBlockHash is None: break ## hard coded value used here for number of blocks to lag behind with persistent state if len(toProcess) == 20: ## advance cached state _processBlock(host, state, wallet, ownedAccounts, blockHash, 'committed', out=out) popped = toProcess.popleft() blockIndex += 1 blockHash = popped mostRecentHash = nextBlockHash toProcess.append(mostRecentHash) PickledCache.Save((blockIndex, blockHash, state), stateVersion, cacheDirectory, 'State') PickledCache.Save(ownedAccounts, ownedAccountsVersion, cacheDirectory, 'OwnedAccounts') print("Committed state updated to start of block {}".format( state._currentBlockIndex), file=out) while len(toProcess) > 0: ## advance in memory state _processBlock(host, state, wallet, ownedAccounts, blockHash, 'in memory', out=out) popped = toProcess.popleft() blockIndex += 1 blockHash = popped _processBlock(host, state, wallet, ownedAccounts, blockHash, 'in memory', out=out) blockIndex += 1 assert state._currentBlockIndex == blockIndex print("In memory state updated to end of block {}".format( state._currentBlockIndex - 1), file=out) # note that the best block chain may have changed during the above # and so the following set of memory pool transactions may not correspond to the actual block chain endpoint we synchronised to # and this may then result in us trying to make double spends, in certain situations # the host should then refuse these transactions, and so this is not a disaster # (and double spend situations can probably also arise more generally in the case of block chain forks, with it not possible for us to always prevent this) # but we can potentially be more careful about this by checking best block chain after getting memory pool transactions # and restarting the block chain traversal if this does not match up memPoolTransactions = host.getMemPoolTransactions() _processTransactions(state, wallet, ownedAccounts, memPoolTransactions, includePending, 'in memory pool', out) return state, ownedAccounts