Browse Source

Merge pull request #2539 from gavinandresen/paymentrequest

Payment Protocol Work
0.10
Gavin Andresen 12 years ago
parent
commit
e62f8d72f3
  1. 28
      bitcoin-qt.pro
  2. 2
      contrib/gitian-descriptors/README
  3. 16
      contrib/gitian-descriptors/gitian-win32.yml
  4. 22
      contrib/gitian-descriptors/gitian.yml
  5. 37
      contrib/gitian-descriptors/protobuf-win32.yml
  6. 14
      contrib/gitian-descriptors/qt-win32.yml
  7. 8
      doc/readme-qt.md
  8. 96
      share/qt/Info.plist
  9. 35
      share/qt/protobuf.pri
  10. 13
      src/bitcoind.cpp
  11. 2
      src/chainparams.h
  12. 5
      src/init.cpp
  13. 35
      src/qt/addresstablemodel.cpp
  14. 85
      src/qt/bitcoin.cpp
  15. 5
      src/qt/bitcoinamountfield.cpp
  16. 3
      src/qt/bitcoinamountfield.h
  17. 20
      src/qt/bitcoingui.cpp
  18. 5
      src/qt/bitcoingui.h
  19. 792
      src/qt/forms/sendcoinsentry.ui
  20. 3
      src/qt/guiconstants.h
  21. 8
      src/qt/guiutil.cpp
  22. 3
      src/qt/guiutil.h
  23. 11
      src/qt/optionsmodel.cpp
  24. 1
      src/qt/optionsmodel.h
  25. 46
      src/qt/paymentrequest.proto
  26. 204
      src/qt/paymentrequestplus.cpp
  27. 41
      src/qt/paymentrequestplus.h
  28. 527
      src/qt/paymentserver.cpp
  29. 64
      src/qt/paymentserver.h
  30. 47
      src/qt/sendcoinsdialog.cpp
  31. 3
      src/qt/sendcoinsdialog.h
  32. 56
      src/qt/sendcoinsentry.cpp
  33. 14
      src/qt/sendcoinsentry.h
  34. 3
      src/qt/splashscreen.cpp
  35. 307
      src/qt/test/paymentrequestdata.h
  36. 109
      src/qt/test/paymentservertests.cpp
  37. 29
      src/qt/test/paymentservertests.h
  38. 37
      src/qt/test/test_main.cpp
  39. 32
      src/qt/transactiondesc.cpp
  40. 4
      src/qt/walletframe.cpp
  41. 5
      src/qt/walletframe.h
  42. 98
      src/qt/walletmodel.cpp
  43. 16
      src/qt/walletmodel.h
  44. 4
      src/qt/walletstack.cpp
  45. 3
      src/qt/walletstack.h
  46. 4
      src/qt/walletview.cpp
  47. 3
      src/qt/walletview.h
  48. 6
      src/rpcdump.cpp
  49. 2
      src/rpcrawtransaction.cpp
  50. 50
      src/rpcwallet.cpp
  51. 29
      src/util.cpp
  52. 31
      src/wallet.cpp
  53. 21
      src/wallet.h
  54. 20
      src/walletdb.cpp
  55. 4
      src/walletdb.h

28
bitcoin-qt.pro

@ -16,21 +16,29 @@ CONFIG += thread
# or when linking against a specific BerkelyDB version: BDB_LIB_SUFFIX=-4.8 # or when linking against a specific BerkelyDB version: BDB_LIB_SUFFIX=-4.8
# Dependency library locations can be customized with: # Dependency library locations can be customized with:
# BOOST_INCLUDE_PATH, BOOST_LIB_PATH, BDB_INCLUDE_PATH, # BOOST_INCLUDE_PATH BOOST_LIB_PATH,
# BDB_LIB_PATH, OPENSSL_INCLUDE_PATH and OPENSSL_LIB_PATH respectively # BDB_INCLUDE_PATH BDB_LIB_PATH,
# OPENSSL_INCLUDE_PATH OPENSSL_LIB_PATH
# PROTOBUF_INCLUDE_PATH PROTOBUF_LIB_PATH
# PROTOC : protocol buffer compiler tool
OBJECTS_DIR = build OBJECTS_DIR = build
MOC_DIR = build MOC_DIR = build
UI_DIR = build UI_DIR = build
PROTO_DIR = build
PROTO_PATH = src/qt
contains(BITCOIN_QT_TEST, 1) { contains(BITCOIN_QT_TEST, 1) {
OBJECTS_DIR = build_test OBJECTS_DIR = build_test
MOC_DIR = build_test MOC_DIR = build_test
UI_DIR = build_test UI_DIR = build_test
PROTO_DIR = build_test
SOURCES += src/qt/test/test_main.cpp \ SOURCES += src/qt/test/test_main.cpp \
src/qt/test/uritests.cpp src/qt/test/uritests.cpp \
HEADERS += src/qt/test/uritests.h src/qt/test/paymentservertests.cpp
HEADERS += src/qt/test/uritests.h \
src/qt/test/paymentservertests.h
DEPENDPATH += src/qt/test DEPENDPATH += src/qt/test
QT += testlib QT += testlib
TARGET = bitcoin-qt_test TARGET = bitcoin-qt_test
@ -219,6 +227,7 @@ HEADERS += src/qt/bitcoingui.h \
src/qt/askpassphrasedialog.h \ src/qt/askpassphrasedialog.h \
src/protocol.h \ src/protocol.h \
src/qt/notificator.h \ src/qt/notificator.h \
src/qt/paymentrequestplus.h \
src/qt/paymentserver.h \ src/qt/paymentserver.h \
src/allocators.h \ src/allocators.h \
src/ui_interface.h \ src/ui_interface.h \
@ -297,6 +306,7 @@ SOURCES += src/qt/bitcoin.cpp \
src/qt/askpassphrasedialog.cpp \ src/qt/askpassphrasedialog.cpp \
src/protocol.cpp \ src/protocol.cpp \
src/qt/notificator.cpp \ src/qt/notificator.cpp \
src/qt/paymentrequestplus.cpp \
src/qt/paymentserver.cpp \ src/qt/paymentserver.cpp \
src/qt/rpcconsole.cpp \ src/qt/rpcconsole.cpp \
src/noui.cpp \ src/noui.cpp \
@ -320,13 +330,15 @@ FORMS += src/qt/forms/sendcoinsdialog.ui \
src/qt/forms/optionsdialog.ui \ src/qt/forms/optionsdialog.ui \
src/qt/forms/intro.ui src/qt/forms/intro.ui
PROTOS = src/qt/paymentrequest.proto
include(share/qt/protobuf.pri)
contains(USE_QRCODE, 1) { contains(USE_QRCODE, 1) {
HEADERS += src/qt/qrcodedialog.h HEADERS += src/qt/qrcodedialog.h
SOURCES += src/qt/qrcodedialog.cpp SOURCES += src/qt/qrcodedialog.cpp
FORMS += src/qt/forms/qrcodedialog.ui FORMS += src/qt/forms/qrcodedialog.ui
} }
# Todo: Remove this line when switching to Qt5, as that option was removed # Todo: Remove this line when switching to Qt5, as that option was removed
CODECFORTR = UTF-8 CODECFORTR = UTF-8
@ -420,9 +432,9 @@ macx:QMAKE_CXXFLAGS_THREAD += -pthread
macx:QMAKE_INFO_PLIST = share/qt/Info.plist macx:QMAKE_INFO_PLIST = share/qt/Info.plist
# Set libraries and includes at end, to use platform-defined defaults if not overridden # Set libraries and includes at end, to use platform-defined defaults if not overridden
INCLUDEPATH += $$BOOST_INCLUDE_PATH $$BDB_INCLUDE_PATH $$OPENSSL_INCLUDE_PATH $$QRENCODE_INCLUDE_PATH INCLUDEPATH += $$BOOST_INCLUDE_PATH $$BDB_INCLUDE_PATH $$OPENSSL_INCLUDE_PATH $$PROTOBUF_INCLUDE_PATH $$QRENCODE_INCLUDE_PATH
LIBS += $$join(BOOST_LIB_PATH,,-L,) $$join(BDB_LIB_PATH,,-L,) $$join(OPENSSL_LIB_PATH,,-L,) $$join(QRENCODE_LIB_PATH,,-L,) LIBS += $$join(BOOST_LIB_PATH,,-L,) $$join(BDB_LIB_PATH,,-L,) $$join(OPENSSL_LIB_PATH,,-L,) $$join(PROTOBUF_LIB_PATH,,-L,) $$join(QRENCODE_LIB_PATH,,-L,)
LIBS += -lssl -lcrypto -ldb_cxx$$BDB_LIB_SUFFIX LIBS += -lssl -lcrypto -ldb_cxx$$BDB_LIB_SUFFIX -lprotobuf
# -lgdi32 has to happen after -lcrypto (see #681) # -lgdi32 has to happen after -lcrypto (see #681)
win32:LIBS += -lws2_32 -lshlwapi -lmswsock -lole32 -loleaut32 -luuid -lgdi32 win32:LIBS += -lws2_32 -lshlwapi -lmswsock -lole32 -loleaut32 -luuid -lgdi32
LIBS += -lboost_system$$BOOST_LIB_SUFFIX -lboost_filesystem$$BOOST_LIB_SUFFIX -lboost_program_options$$BOOST_LIB_SUFFIX -lboost_thread$$BOOST_THREAD_LIB_SUFFIX LIBS += -lboost_system$$BOOST_LIB_SUFFIX -lboost_filesystem$$BOOST_LIB_SUFFIX -lboost_program_options$$BOOST_LIB_SUFFIX -lboost_thread$$BOOST_THREAD_LIB_SUFFIX

2
contrib/gitian-descriptors/README

@ -32,6 +32,7 @@ Once you've got the right hardware and software:
wget 'https://downloads.sourceforge.net/project/libpng/zlib/1.2.6/zlib-1.2.6.tar.gz' wget 'https://downloads.sourceforge.net/project/libpng/zlib/1.2.6/zlib-1.2.6.tar.gz'
wget 'https://downloads.sourceforge.net/project/libpng/libpng15/older-releases/1.5.9/libpng-1.5.9.tar.gz' wget 'https://downloads.sourceforge.net/project/libpng/libpng15/older-releases/1.5.9/libpng-1.5.9.tar.gz'
wget 'http://releases.qt-project.org/qt4/source/qt-everywhere-opensource-src-4.8.3.tar.gz' wget 'http://releases.qt-project.org/qt4/source/qt-everywhere-opensource-src-4.8.3.tar.gz'
wget 'http://protobuf.googlecode.com/files/protobuf-2.5.0.tar.bz2'
cd ../.. cd ../..
cd gitian-builder cd gitian-builder
@ -50,6 +51,7 @@ Once you've got the right hardware and software:
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/boost-win32.yml ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/boost-win32.yml
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/deps-win32.yml ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/deps-win32.yml
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/qt-win32.yml ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/qt-win32.yml
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/protobuf-win32.yml
# Build Win32 release: # Build Win32 release:
./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/gitian-win32.yml ./bin/gbuild --commit bitcoin=HEAD ../bitcoin/contrib/gitian-descriptors/gitian-win32.yml

16
contrib/gitian-descriptors/gitian-win32.yml

@ -15,16 +15,18 @@ remotes:
- "url": "https://github.com/bitcoin/bitcoin.git" - "url": "https://github.com/bitcoin/bitcoin.git"
"dir": "bitcoin" "dir": "bitcoin"
files: files:
- "qt-win32-4.8.3-gitian-r1.zip" - "qt-win32-4.8.3-gitian-r2.zip"
- "boost-win32-1.50.0-gitian2.zip" - "boost-win32-1.50.0-gitian2.zip"
- "bitcoin-deps-0.0.5.zip" - "bitcoin-deps-0.0.5.zip"
- "protobuf-win32-2.5.0-gitian-r1.zip"
script: | script: |
# #
mkdir $HOME/qt export QTDIR=$HOME/qt
cd $HOME/qt mkdir $QTDIR
unzip ../build/qt-win32-4.8.3-gitian-r1.zip cd $QTDIR
unzip ../build/qt-win32-4.8.3-gitian-r2.zip
cd $HOME/build/ cd $HOME/build/
export PATH=$HOME/qt/bin/:$PATH export PATH=$QTDIR/bin/:$PATH
# #
mkdir boost_1_50_0 mkdir boost_1_50_0
cd boost_1_50_0 cd boost_1_50_0
@ -41,6 +43,8 @@ script: |
# #
unzip bitcoin-deps-0.0.5.zip unzip bitcoin-deps-0.0.5.zip
# #
unzip protobuf-win32-2.5.0-gitian-r1.zip
#
find -type f | xargs touch --date="$REFERENCE_DATETIME" find -type f | xargs touch --date="$REFERENCE_DATETIME"
# #
cd bitcoin cd bitcoin
@ -51,7 +55,7 @@ script: |
export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1
export FAKETIME=$REFERENCE_DATETIME export FAKETIME=$REFERENCE_DATETIME
export TZ=UTC export TZ=UTC
$HOME/qt/src/bin/qmake -spec unsupported/win32-g++-cross MINIUPNPC_LIB_PATH=$HOME/build/miniupnpc MINIUPNPC_INCLUDE_PATH=$HOME/build/ BDB_LIB_PATH=$HOME/build/db-4.8.30.NC/build_unix BDB_INCLUDE_PATH=$HOME/build/db-4.8.30.NC/build_unix BOOST_LIB_PATH=$HOME/build/boost_1_50_0/stage/lib BOOST_INCLUDE_PATH=$HOME/build/boost_1_50_0 BOOST_LIB_SUFFIX=-mt-s BOOST_THREAD_LIB_SUFFIX=_win32-mt-s OPENSSL_LIB_PATH=$HOME/build/openssl-1.0.1c OPENSSL_INCLUDE_PATH=$HOME/build/openssl-1.0.1c/include QRENCODE_LIB_PATH=$HOME/build/qrencode-3.2.0/.libs QRENCODE_INCLUDE_PATH=$HOME/build/qrencode-3.2.0 USE_QRCODE=1 INCLUDEPATH=$HOME/build DEFINES=BOOST_THREAD_USE_LIB BITCOIN_NEED_QT_PLUGINS=1 QMAKE_LRELEASE=lrelease QMAKE_CXXFLAGS=-frandom-seed=bitcoin USE_BUILD_INFO=1 $QTDIR/bin/qmake -spec unsupported/win32-g++-cross PROTOBUF_LIB_PATH=$HOME/build/protobuf-win32 PROTOBUF_INCLUDE_PATH=$HOME/build/protobuf-win32 PROTOC=$HOME/build/protobuf-win32/protoc MINIUPNPC_LIB_PATH=$HOME/build/miniupnpc MINIUPNPC_INCLUDE_PATH=$HOME/build/ BDB_LIB_PATH=$HOME/build/db-4.8.30.NC/build_unix BDB_INCLUDE_PATH=$HOME/build/db-4.8.30.NC/build_unix BOOST_LIB_PATH=$HOME/build/boost_1_50_0/stage/lib BOOST_INCLUDE_PATH=$HOME/build/boost_1_50_0 BOOST_LIB_SUFFIX=-mt-s BOOST_THREAD_LIB_SUFFIX=_win32-mt-s OPENSSL_LIB_PATH=$HOME/build/openssl-1.0.1c OPENSSL_INCLUDE_PATH=$HOME/build/openssl-1.0.1c/include QRENCODE_LIB_PATH=$HOME/build/qrencode-3.2.0/.libs QRENCODE_INCLUDE_PATH=$HOME/build/qrencode-3.2.0 USE_QRCODE=1 INCLUDEPATH=$HOME/build DEFINES=BOOST_THREAD_USE_LIB BITCOIN_NEED_QT_PLUGINS=1 QMAKE_LRELEASE=lrelease QMAKE_CXXFLAGS=-frandom-seed=bitcoin USE_BUILD_INFO=1
make $MAKEOPTS make $MAKEOPTS
cp release/bitcoin-qt.exe $OUTDIR/ cp release/bitcoin-qt.exe $OUTDIR/
# #

22
contrib/gitian-descriptors/gitian.yml

@ -18,38 +18,44 @@ packages:
- "unzip" - "unzip"
- "pkg-config" - "pkg-config"
- "libpng12-dev" - "libpng12-dev"
reference_datetime: "2011-01-30 00:00:00" reference_datetime: "2013-06-01 00:00:00"
remotes: remotes:
- "url": "https://github.com/bitcoin/bitcoin.git" - "url": "https://github.com/bitcoin/bitcoin.git"
"dir": "bitcoin" "dir": "bitcoin"
files: files:
- "miniupnpc-1.6.tar.gz" - "miniupnpc-1.6.tar.gz"
- "qrencode-3.2.0.tar.bz2" - "qrencode-3.2.0.tar.bz2"
- "protobuf-2.5.0.tar.bz2"
script: | script: |
INSTDIR="$HOME/install" INSTDIR="$HOME/install"
export LIBRARY_PATH="$INSTDIR/lib" export LIBRARY_PATH="$INSTDIR/lib"
# #
tar xzf miniupnpc-1.6.tar.gz tar xzfm miniupnpc-1.6.tar.gz
cd miniupnpc-1.6 cd miniupnpc-1.6
INSTALLPREFIX=$INSTDIR make $MAKEOPTS install INSTALLPREFIX=$INSTDIR make $MAKEOPTS install
cd .. cd ..
# #
tar xjf qrencode-3.2.0.tar.bz2 tar xjfm qrencode-3.2.0.tar.bz2
cd qrencode-3.2.0 cd qrencode-3.2.0
./configure --prefix=$INSTDIR --enable-static --disable-shared ./configure --prefix=$INSTDIR --enable-static --disable-shared
make $MAKEOPTS install make $MAKEOPTS install
cd .. cd ..
# #
tar xjfm protobuf-2.5.0.tar.bz2
cd protobuf-2.5.0
./configure --prefix=$INSTDIR --enable-static --disable-shared
make $MAKEOPTS install
cd ..
#
cd bitcoin cd bitcoin
mkdir -p $OUTDIR/src mkdir -p $OUTDIR/src
git archive HEAD | tar -x -C $OUTDIR/src git archive HEAD | tar -x -C $OUTDIR/src
cp $OUTDIR/src/doc/README.md $OUTDIR cp $OUTDIR/src/doc/README.md $OUTDIR
cp $OUTDIR/src/COPYING $OUTDIR cp $OUTDIR/src/COPYING $OUTDIR
cd src
make -f makefile.unix STATIC=1 OPENSSL_INCLUDE_PATH="$INSTDIR/include" OPENSSL_LIB_PATH="$INSTDIR/lib" $MAKEOPTS bitcoind USE_UPNP=0 DEBUGFLAGS=
mkdir -p $OUTDIR/bin/$GBUILD_BITS mkdir -p $OUTDIR/bin/$GBUILD_BITS
install -s bitcoind $OUTDIR/bin/$GBUILD_BITS qmake INCLUDEPATH="$INSTDIR/include" LIBS="-L$INSTDIR/lib" PROTOC="$INSTDIR/bin/protoc" PROTOBUF_LIB_PATH="$INSTDIR/lib" PROTOBUF_INCLUDE_PATH="$INSTDIR/include" RELEASE=1 USE_QRCODE=1
cd ..
qmake INCLUDEPATH="$INSTDIR/include" LIBS="-L$INSTDIR/lib" RELEASE=1 USE_QRCODE=1
make $MAKEOPTS make $MAKEOPTS
install bitcoin-qt $OUTDIR/bin/$GBUILD_BITS install bitcoin-qt $OUTDIR/bin/$GBUILD_BITS
cd src
make -f makefile.unix STATIC=1 OPENSSL_INCLUDE_PATH="$INSTDIR/include" OPENSSL_LIB_PATH="$INSTDIR/lib" $MAKEOPTS bitcoind USE_UPNP=0 DEBUGFLAGS=
install -s bitcoind $OUTDIR/bin/$GBUILD_BITS

37
contrib/gitian-descriptors/protobuf-win32.yml

@ -0,0 +1,37 @@
---
name: "protobuf-win32"
suites:
- "lucid"
architectures:
- "i386"
packages:
- "mingw32"
- "zip"
- "faketime"
reference_datetime: "2013-04-15 00:00:00"
remotes: []
files:
- "protobuf-2.5.0.tar.bz2"
script: |
#
export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1
export FAKETIME=$REFERENCE_DATETIME
export TZ=UTC
#
tar xjf protobuf-2.5.0.tar.bz2
cd protobuf-2.5.0
# First: build a native (linux) protoc
./configure --enable-shared=no --disable-dependency-tracking
make
mkdir -p host
cp src/protoc host
# Now recompile with the mingw cross-compiler:
make distclean
./configure --enable-shared=no --disable-dependency-tracking --with-protoc=$(pwd)/host/protoc --host=i586-mingw32msvc CXXFLAGS=-frandom-seed=11
make
cd ..
mkdir -p protobuf-win32
cp protobuf-2.5.0/host/protoc protobuf-win32/protoc
cp protobuf-2.5.0/src/.libs/libprotobuf.a protobuf-win32/libprotobuf.a
cp -r protobuf-2.5.0/src/google protobuf-win32/
zip -r $OUTDIR/protobuf-win32-2.5.0-gitian-r1.zip protobuf-win32

14
contrib/gitian-descriptors/qt-win32.yml

@ -12,11 +12,14 @@ reference_datetime: "2011-01-30 00:00:00"
remotes: [] remotes: []
files: files:
- "qt-everywhere-opensource-src-4.8.3.tar.gz" - "qt-everywhere-opensource-src-4.8.3.tar.gz"
- "bitcoin-deps-0.0.5.zip"
script: | script: |
INSTDIR="$HOME/qt/" INSTDIR="$HOME/qt/"
mkdir $INSTDIR mkdir $INSTDIR
SRCDIR="$INSTDIR/src/" #
mkdir $SRCDIR # Need mingw-compiled openssl from bitcoin-deps:
unzip bitcoin-deps-0.0.5.zip
DEPSDIR=`pwd`
# #
tar xzf qt-everywhere-opensource-src-4.8.3.tar.gz tar xzf qt-everywhere-opensource-src-4.8.3.tar.gz
cd qt-everywhere-opensource-src-4.8.3 cd qt-everywhere-opensource-src-4.8.3
@ -40,15 +43,14 @@ script: |
#export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 #export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1
export FAKETIME=$REFERENCE_DATETIME export FAKETIME=$REFERENCE_DATETIME
export TZ=UTC export TZ=UTC
./configure -prefix $INSTDIR -confirm-license -release -opensource -static -no-qt3support -xplatform unsupported/win32-g++-cross -no-multimedia -no-audio-backend -no-phonon -no-phonon-backend -no-declarative -no-script -no-scripttools -no-javascript-jit -no-webkit -no-svg -no-xmlpatterns -no-sql-sqlite -no-nis -no-cups -no-iconv -no-dbus -no-gif -no-libtiff -no-opengl -nomake examples -nomake demos -nomake docs -no-feature-style-plastique -no-feature-style-cleanlooks -no-feature-style-motif -no-feature-style-cde -no-feature-style-windowsce -no-feature-style-windowsmobile -no-feature-style-s60 # Compile static libraries, and use statically linked openssl (-openssl-linked):
OPENSSL_LIBS="-L$DEPSDIR/openssl-1.0.1c -lssl -lcrypto -lgdi32" ./configure -prefix $INSTDIR -I $DEPSDIR/openssl-1.0.1c/include -confirm-license -release -opensource -static -no-qt3support -xplatform unsupported/win32-g++-cross -no-multimedia -no-audio-backend -no-phonon -no-phonon-backend -no-declarative -no-script -no-scripttools -no-javascript-jit -no-webkit -no-svg -no-xmlpatterns -no-sql-sqlite -no-nis -no-cups -no-iconv -no-dbus -no-gif -no-libtiff -no-opengl -nomake examples -nomake demos -nomake docs -no-feature-style-plastique -no-feature-style-cleanlooks -no-feature-style-motif -no-feature-style-cde -no-feature-style-windowsce -no-feature-style-windowsmobile -no-feature-style-s60 -openssl-linked
find . -name *.prl | xargs -l sed 's|/\.||' -i find . -name *.prl | xargs -l sed 's|/\.||' -i
find . -name *.prl | xargs -l sed 's|/$||' -i find . -name *.prl | xargs -l sed 's|/$||' -i
make $MAKEOPTS install make $MAKEOPTS install
cp -a bin $SRCDIR/
cd $INSTDIR cd $INSTDIR
find . -name *.prl | xargs -l sed 's|/$||' -i find . -name *.prl | xargs -l sed 's|/$||' -i
#sed 's|QMAKE_PRL_LIBS.*|QMAKE_PRL_LIBS = -lQtDeclarative -lQtScript -lQtSvg -lQtSql -lQtXmlPatterns -lQtGui -lgdi32 -lcomdlg32 -loleaut32 -limm32 -lwinmm -lwinspool -lmsimg32 -lQtNetwork -lQtCore -lole32 -luuid -lws2_32 -ladvapi32 -lshell32 -luser32 -lkernel32|' -i imports/Qt/labs/particles/qmlparticlesplugin.prl
# as zip stores file timestamps, use faketime to intercept stat calls to set dates for all files to reference date # as zip stores file timestamps, use faketime to intercept stat calls to set dates for all files to reference date
export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1 export LD_PRELOAD=/usr/lib/faketime/libfaketime.so.1
zip -r $OUTDIR/qt-win32-4.8.3-gitian-r1.zip * zip -r $OUTDIR/qt-win32-4.8.3-gitian-r2.zip *

8
doc/readme-qt.md

@ -18,13 +18,13 @@ for Debian and Ubuntu <= 11.10 :
apt-get install qt4-qmake libqt4-dev build-essential libboost-dev libboost-system-dev \ apt-get install qt4-qmake libqt4-dev build-essential libboost-dev libboost-system-dev \
libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev \ libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev \
libssl-dev libdb4.8++-dev libssl-dev libdb4.8++-dev libprotobuf-dev protobuf-compiler
for Ubuntu >= 12.04 (please read the 'Berkely DB version warning' below): for Ubuntu >= 12.04 (please read the 'Berkely DB version warning' below):
apt-get install qt4-qmake libqt4-dev build-essential libboost-dev libboost-system-dev \ apt-get install qt4-qmake libqt4-dev build-essential libboost-dev libboost-system-dev \
libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev \ libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev \
libssl-dev libdb++-dev libminiupnpc-dev libssl-dev libdb++-dev libminiupnpc-dev libprotobuf-dev protobuf-compiler
For Qt 5 you need the following, otherwise you get an error with lrelease when running qmake: For Qt 5 you need the following, otherwise you get an error with lrelease when running qmake:
@ -48,12 +48,12 @@ An executable named `bitcoin-qt` will be built.
* Execute the following commands in a terminal to get the dependencies using MacPorts * Execute the following commands in a terminal to get the dependencies using MacPorts
sudo port selfupdate sudo port selfupdate
sudo port install boost db48 miniupnpc sudo port install boost db48 miniupnpc protobuf-cpp
* Execute the following commands in a terminal to get the dependencies using HomeBrew: * Execute the following commands in a terminal to get the dependencies using HomeBrew:
brew update brew update
brew install boost miniupnpc openssl berkeley-db4 brew install boost miniupnpc openssl berkeley-db4 protobuf
- If using HomeBrew, edit `bitcoin-qt.pro` to account for library location differences. There's a diff in `contrib/homebrew/bitcoin-qt-pro.patch` that shows what you need to change, or you can just patch by doing - If using HomeBrew, edit `bitcoin-qt.pro` to account for library location differences. There's a diff in `contrib/homebrew/bitcoin-qt-pro.patch` that shows what you need to change, or you can just patch by doing

96
share/qt/Info.plist

@ -2,36 +2,74 @@
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> <!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9"> <plist version="0.9">
<dict> <dict>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>bitcoin.icns</string> <string>bitcoin.icns</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>$VERSION, Copyright © 2009-$YEAR The Bitcoin developers</string> <string>$VERSION, Copyright © 2009-$YEAR The Bitcoin developers</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$VERSION</string> <string>$VERSION</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$VERSION</string> <string>$VERSION</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>Bitcoin-Qt</string> <string>Bitcoin-Qt</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>org.bitcoinfoundation.Bitcoin-Qt</string> <string>org.bitcoinfoundation.Bitcoin-Qt</string>
<key>CFBundleURLTypes</key>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>org.bitcoin.BitcoinPayment</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
</array>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.bitcoin.paymentrequest</string>
<key>UTTypeDescription</key>
<string>Bitcoin payment request</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.mime-type</key>
<string>application/x-bitcoin-payment-request</string>
<key>public.filename-extension</key>
<array> <array>
<dict> <string>bitcoinpaymentrequest</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>org.bitcoinfoundation.BitcoinPayment</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
</array>
</dict>
</array> </array>
<key>NSHighResolutionCapable</key> </dict>
<true/> </dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>org.bitcoin.paymentrequest</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>NSHighResolutionCapable</key>
<true/>
</dict> </dict>
</plist> </plist>

35
share/qt/protobuf.pri

@ -0,0 +1,35 @@
# Based on: http://code.google.com/p/ostinato/source/browse/protobuf.pri
#
# Qt qmake integration with Google Protocol Buffers compiler protoc
#
# To compile protocol buffers with qt qmake, specify PROTOS variable and
# include this file
#
# Example:
# PROTOS = a.proto b.proto
# include(protobuf.pri)
#
# Set PROTO_PATH if you need to set the protoc --proto_path search path
# Set PROTOC to the path to the protoc compiler if it is not in your $PATH
#
isEmpty(PROTO_DIR):PROTO_DIR = .
isEmpty(PROTOC):PROTOC = protoc
PROTOPATHS =
for(p, PROTO_PATH):PROTOPATHS += --proto_path=$${p}
protobuf_decl.name = protobuf header
protobuf_decl.input = PROTOS
protobuf_decl.output = $${PROTO_DIR}/${QMAKE_FILE_BASE}.pb.h
protobuf_decl.commands = $${PROTOC} --cpp_out="$${PROTO_DIR}" $${PROTOPATHS} --proto_path=${QMAKE_FILE_IN_PATH} ${QMAKE_FILE_NAME}
protobuf_decl.variable_out = GENERATED_FILES
QMAKE_EXTRA_COMPILERS += protobuf_decl
protobuf_impl.name = protobuf implementation
protobuf_impl.input = PROTOS
protobuf_impl.output = $${PROTO_DIR}/${QMAKE_FILE_BASE}.pb.cc
protobuf_impl.depends = $${PROTO_DIR}/${QMAKE_FILE_BASE}.pb.h
protobuf_impl.commands = $$escape_expand(\\n)
protobuf_impl.variable_out = GENERATED_SOURCES
QMAKE_EXTRA_COMPILERS += protobuf_impl

13
src/bitcoind.cpp

@ -39,10 +39,15 @@ bool AppInit(int argc, char* argv[])
ParseParameters(argc, argv); ParseParameters(argc, argv);
if (!boost::filesystem::is_directory(GetDataDir(false))) if (!boost::filesystem::is_directory(GetDataDir(false)))
{ {
fprintf(stderr, "Error: Specified directory does not exist\n"); fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str());
Shutdown(); return false;
} }
ReadConfigFile(mapArgs, mapMultiArgs); ReadConfigFile(mapArgs, mapMultiArgs);
// Check for -testnet or -regtest parameter (TestNet() calls are only valid after this clause)
if (!SelectParamsFromCommandLine()) {
fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n");
return false;
}
if (mapArgs.count("-?") || mapArgs.count("--help")) if (mapArgs.count("-?") || mapArgs.count("--help"))
{ {
@ -67,10 +72,6 @@ bool AppInit(int argc, char* argv[])
if (fCommandLine) if (fCommandLine)
{ {
if (!SelectParamsFromCommandLine()) {
fprintf(stderr, "Error: invalid combination of -regtest and -testnet.\n");
return false;
}
int ret = CommandLineRPC(argc, argv); int ret = CommandLineRPC(argc, argv);
exit(ret); exit(ret);
} }

2
src/chainparams.h

@ -39,6 +39,8 @@ public:
MAIN, MAIN,
TESTNET, TESTNET,
REGTEST, REGTEST,
MAX_NETWORK_TYPES
}; };
enum Base58Type { enum Base58Type {

5
src/init.cpp

@ -373,9 +373,6 @@ bool AppInit2(boost::thread_group& threadGroup)
// ********************************************************* Step 2: parameter interactions // ********************************************************* Step 2: parameter interactions
Checkpoints::fEnabled = GetBoolArg("-checkpoints", true); Checkpoints::fEnabled = GetBoolArg("-checkpoints", true);
if (!SelectParamsFromCommandLine()) {
return InitError("Invalid combination of -testnet and -regtest.");
}
if (mapArgs.count("-bind")) { if (mapArgs.count("-bind")) {
// when specifying an explicit binding address, you want to listen on it // when specifying an explicit binding address, you want to listen on it
@ -898,7 +895,7 @@ bool AppInit2(boost::thread_group& threadGroup)
CPubKey newDefaultKey; CPubKey newDefaultKey;
if (pwalletMain->GetKeyFromPool(newDefaultKey, false)) { if (pwalletMain->GetKeyFromPool(newDefaultKey, false)) {
pwalletMain->SetDefaultKey(newDefaultKey); pwalletMain->SetDefaultKey(newDefaultKey);
if (!pwalletMain->SetAddressBookName(pwalletMain->vchDefaultKey.GetID(), "")) if (!pwalletMain->SetAddressBook(pwalletMain->vchDefaultKey.GetID(), "", "receive"))
strErrors << _("Cannot write default address") << "\n"; strErrors << _("Cannot write default address") << "\n";
} }

35
src/qt/addresstablemodel.cpp

@ -59,12 +59,22 @@ public:
cachedAddressTable.clear(); cachedAddressTable.clear();
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
BOOST_FOREACH(const PAIRTYPE(CTxDestination, std::string)& item, wallet->mapAddressBook) BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
{ {
const CBitcoinAddress& address = item.first; const CBitcoinAddress& address = item.first;
const std::string& strName = item.second;
bool fMine = IsMine(*wallet, address.Get()); AddressTableEntry::Type addressType;
cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending, const std::string& strPurpose = item.second.purpose;
if (strPurpose == "send") addressType = AddressTableEntry::Sending;
else if (strPurpose == "receive") addressType = AddressTableEntry::Receiving;
else if (strPurpose == "unknown") {
bool fMine = IsMine(*wallet, address.Get());
addressType = (fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending);
}
else continue; // "refund" addresses aren't shown, and change addresses aren't in mapAddressBook at all.
const std::string& strName = item.second.name;
cachedAddressTable.append(AddressTableEntry(addressType,
QString::fromStdString(strName), QString::fromStdString(strName),
QString::fromStdString(address.ToString()))); QString::fromStdString(address.ToString())));
} }
@ -215,7 +225,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
if(!index.isValid()) if(!index.isValid())
return false; return false;
AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer()); AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
editStatus = OK; editStatus = OK;
if(role == Qt::EditRole) if(role == Qt::EditRole)
@ -229,7 +239,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
editStatus = NO_CHANGES; editStatus = NO_CHANGES;
return false; return false;
} }
wallet->SetAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get(), value.toString().toStdString()); wallet->SetAddressBook(CBitcoinAddress(rec->address.toStdString()).Get(), value.toString().toStdString(), strPurpose);
break; break;
case Address: case Address:
// Do nothing, if old address == new address // Do nothing, if old address == new address
@ -257,9 +267,9 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
// Remove old entry // Remove old entry
wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get()); wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get());
// Add new entry with new address // Add new entry with new address
wallet->SetAddressBookName(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString()); wallet->SetAddressBook(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString(), strPurpose);
} }
} }
break; break;
@ -368,7 +378,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
// Add entry // Add entry
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
wallet->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel); wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel,
(type == Send ? "send" : "receive"));
} }
return QString::fromStdString(strAddress); return QString::fromStdString(strAddress);
} }
@ -385,7 +396,7 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent
} }
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get()); wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get());
} }
return true; return true;
} }
@ -397,10 +408,10 @@ QString AddressTableModel::labelForAddress(const QString &address) const
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
CBitcoinAddress address_parsed(address.toStdString()); CBitcoinAddress address_parsed(address.toStdString());
std::map<CTxDestination, std::string>::iterator mi = wallet->mapAddressBook.find(address_parsed.Get()); std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(address_parsed.Get());
if (mi != wallet->mapAddressBook.end()) if (mi != wallet->mapAddressBook.end())
{ {
return QString::fromStdString(mi->second); return QString::fromStdString(mi->second.name);
} }
} }
return QString(); return QString();

85
src/qt/bitcoin.cpp

@ -151,13 +151,39 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans
QApplication::installTranslator(&translator); QApplication::installTranslator(&translator);
} }
/* qDebug() message handler --> debug.log */
#if QT_VERSION < 0x050000
void DebugMessageHandler(QtMsgType type, const char * msg)
{
OutputDebugStringF("%s\n", msg);
}
#else
void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg)
{
OutputDebugStringF("%s\n", qPrintable(msg));
}
#endif
#ifndef BITCOIN_QT_TEST #ifndef BITCOIN_QT_TEST
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
bool fMissingDatadir = false;
bool fSelParFromCLFailed = false;
fHaveGUI = true; fHaveGUI = true;
// Command-line options take precedence: // Command-line options take precedence:
ParseParameters(argc, argv); ParseParameters(argc, argv);
// ... then bitcoin.conf:
if (!boost::filesystem::is_directory(GetDataDir(false))) {
fMissingDatadir = true;
} else {
ReadConfigFile(mapArgs, mapMultiArgs);
}
// Check for -testnet or -regtest parameter (TestNet() calls are only valid after this clause)
if (!SelectParamsFromCommandLine()) {
fSelParFromCLFailed = true;
}
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000
// Internal string conversion is all UTF-8 // Internal string conversion is all UTF-8
@ -175,7 +201,7 @@ int main(int argc, char *argv[])
// as it is used to locate QSettings) // as it is used to locate QSettings)
QApplication::setOrganizationName("Bitcoin"); QApplication::setOrganizationName("Bitcoin");
QApplication::setOrganizationDomain("bitcoin.org"); QApplication::setOrganizationDomain("bitcoin.org");
if (GetBoolArg("-testnet", false)) // Separate UI settings for testnet if (TestNet()) // Separate UI settings for testnet
QApplication::setApplicationName("Bitcoin-Qt-testnet"); QApplication::setApplicationName("Bitcoin-Qt-testnet");
else else
QApplication::setApplicationName("Bitcoin-Qt"); QApplication::setApplicationName("Bitcoin-Qt");
@ -184,28 +210,34 @@ int main(int argc, char *argv[])
QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator;
initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);
// User language is set up: pick a data directory
Intro::pickDataDirectory();
// Do this early as we don't want to bother initializing if we are just calling IPC // Do this early as we don't want to bother initializing if we are just calling IPC
// ... but do it after creating app, so QCoreApplication::arguments is initialized: // ... but do it after creating app and setting up translations, so errors are
if (PaymentServer::ipcSendCommandLine()) // translated properly.
if (PaymentServer::ipcSendCommandLine(argc, argv))
exit(0); exit(0);
PaymentServer* paymentServer = new PaymentServer(&app);
// Install global event filter that makes sure that long tooltips can be word-wrapped
app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
// ... then bitcoin.conf: // Now that translations are initialized check for errors and allow a translatable error message
if (!boost::filesystem::is_directory(GetDataDir(false))) if (fMissingDatadir) {
{
QMessageBox::critical(0, QObject::tr("Bitcoin"), QMessageBox::critical(0, QObject::tr("Bitcoin"),
QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"]))); QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"])));
return 1; return 1;
} }
ReadConfigFile(mapArgs, mapMultiArgs); else if (fSelParFromCLFailed) {
QMessageBox::critical(0, QObject::tr("Bitcoin"), QObject::tr("Error: Invalid combination of -regtest and -testnet."));
return 1;
}
// ... then GUI settings: // Start up the payment server early, too, so impatient users that click on
// bitcoin: links repeatedly have their payment requests routed to this process:
PaymentServer* paymentServer = new PaymentServer(&app);
// User language is set up: pick a data directory
Intro::pickDataDirectory();
// Install global event filter that makes sure that long tooltips can be word-wrapped
app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
// ... now GUI settings:
OptionsModel optionsModel; OptionsModel optionsModel;
// Subscribe to global signals from core // Subscribe to global signals from core
@ -223,6 +255,13 @@ int main(int argc, char *argv[])
return 1; return 1;
} }
// Install qDebug() message handler to route to debug.log:
#if QT_VERSION < 0x050000
qInstallMsgHandler(DebugMessageHandler);
#else
qInstallMessageHandler(DebugMessageHandler);
#endif
SplashScreen splash(QPixmap(), 0); SplashScreen splash(QPixmap(), 0);
if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false)) if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false))
{ {
@ -245,7 +284,7 @@ int main(int argc, char *argv[])
boost::thread_group threadGroup; boost::thread_group threadGroup;
BitcoinGUI window(GetBoolArg("-testnet", false), 0); BitcoinGUI window(TestNet(), 0);
guiref = &window; guiref = &window;
QTimer* pollShutdownTimer = new QTimer(guiref); QTimer* pollShutdownTimer = new QTimer(guiref);
@ -260,6 +299,9 @@ int main(int argc, char *argv[])
optionsModel.Upgrade(); // Must be done after AppInit2 optionsModel.Upgrade(); // Must be done after AppInit2
PaymentServer::LoadRootCAs();
paymentServer->initNetManager(optionsModel);
if (splashref) if (splashref)
splash.finish(&window); splash.finish(&window);
@ -281,8 +323,15 @@ int main(int argc, char *argv[])
} }
// Now that initialization/startup is done, process any command-line // Now that initialization/startup is done, process any command-line
// bitcoin: URIs // bitcoin: URIs or payment requests:
QObject::connect(paymentServer, SIGNAL(receivedURI(QString)), &window, SLOT(handleURI(QString))); QObject::connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)),
&window, SLOT(handlePaymentRequest(SendCoinsRecipient)));
QObject::connect(&walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)),
paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray)));
QObject::connect(paymentServer, SIGNAL(receivedPaymentACK(QString)),
&window, SLOT(showPaymentACK(QString)));
QObject::connect(paymentServer, SIGNAL(reportError(QString, QString, unsigned int)),
guiref, SLOT(message(QString, QString, unsigned int)));
QTimer::singleShot(100, paymentServer, SLOT(uiReady())); QTimer::singleShot(100, paymentServer, SLOT(uiReady()));
app.exec(); app.exec();

5
src/qt/bitcoinamountfield.cpp

@ -130,6 +130,11 @@ void BitcoinAmountField::setValue(qint64 value)
setText(BitcoinUnits::format(currentUnit, value)); setText(BitcoinUnits::format(currentUnit, value));
} }
void BitcoinAmountField::setReadOnly(bool fReadeOnly)
{
// TODO ...
}
void BitcoinAmountField::unitChanged(int idx) void BitcoinAmountField::unitChanged(int idx)
{ {
// Use description tooltip for current unit for the combobox // Use description tooltip for current unit for the combobox

3
src/qt/bitcoinamountfield.h

@ -22,6 +22,9 @@ public:
qint64 value(bool *valid=0) const; qint64 value(bool *valid=0) const;
void setValue(qint64 value); void setValue(qint64 value);
/** Make read-only **/
void setReadOnly(bool fReadOnly);
/** Mark current value as invalid in UI. */ /** Mark current value as invalid in UI. */
void setValid(bool valid); void setValid(bool valid);
/** Perform input validation, mark field as invalid if entered value is not valid. */ /** Perform input validation, mark field as invalid if entered value is not valid. */

20
src/qt/bitcoingui.cpp

@ -45,6 +45,7 @@
#include <QDragEnterEvent> #include <QDragEnterEvent>
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000
#include <QUrl> #include <QUrl>
#include <QTextDocument>
#endif #endif
#include <QMimeData> #include <QMimeData>
#include <QStyle> #include <QStyle>
@ -707,7 +708,8 @@ void BitcoinGUI::dropEvent(QDropEvent *event)
QList<QUrl> uris = event->mimeData()->urls(); QList<QUrl> uris = event->mimeData()->urls();
foreach(const QUrl &uri, uris) foreach(const QUrl &uri, uris)
{ {
if (walletFrame->handleURI(uri.toString())) SendCoinsRecipient r;
if (GUIUtil::parseBitcoinURI(uri, &r) && walletFrame->handlePaymentRequest(r))
nValidUrisFound++; nValidUrisFound++;
} }
@ -734,12 +736,18 @@ bool BitcoinGUI::eventFilter(QObject *object, QEvent *event)
return QMainWindow::eventFilter(object, event); return QMainWindow::eventFilter(object, event);
} }
void BitcoinGUI::handleURI(QString strURI) void BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient& recipient)
{ {
// URI has to be valid walletFrame->handlePaymentRequest(recipient);
if (!walletFrame->handleURI(strURI)) }
message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
CClientUIInterface::ICON_WARNING); void BitcoinGUI::showPaymentACK(QString msg)
{
#if QT_VERSION < 0x050000
message(tr("Payment acknowledged"), Qt::escape(msg), CClientUIInterface::MODAL);
#else
message(tr("Payment acknowledged"), msg.toHtmlEscaped(), CClientUIInterface::MODAL);
#endif
} }
void BitcoinGUI::setEncryptionStatus(int status) void BitcoinGUI::setEncryptionStatus(int status)

5
src/qt/bitcoingui.h

@ -15,6 +15,7 @@ class TransactionView;
class OverviewPage; class OverviewPage;
class AddressBookPage; class AddressBookPage;
class SendCoinsDialog; class SendCoinsDialog;
class SendCoinsRecipient;
class SignVerifyMessageDialog; class SignVerifyMessageDialog;
class Notificator; class Notificator;
class RPCConsole; class RPCConsole;
@ -151,7 +152,9 @@ public slots:
@param[out] payFee true to pay the fee, false to not pay the fee @param[out] payFee true to pay the fee, false to not pay the fee
*/ */
void askFee(qint64 nFeeRequired, bool *payFee); void askFee(qint64 nFeeRequired, bool *payFee);
void handleURI(QString strURI);
void handlePaymentRequest(const SendCoinsRecipient& recipient);
void showPaymentACK(QString msg);
/** Show incoming transaction notification for new transactions. */ /** Show incoming transaction notification for new transactions. */
void incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address); void incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address);

792
src/qt/forms/sendcoinsentry.ui

@ -1,143 +1,685 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>SendCoinsEntry</class> <class>SendCoinsEntry</class>
<widget class="QFrame" name="SendCoinsEntry"> <widget class="QStackedWidget" name="SendCoinsEntry">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>729</width> <width>729</width>
<height>136</height> <height>150</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>StackedWidget</string>
</property> </property>
<property name="frameShape"> <property name="autoFillBackground">
<enum>QFrame::StyledPanel</enum> <bool>false</bool>
</property> </property>
<property name="frameShadow"> <property name="currentIndex">
<enum>QFrame::Sunken</enum> <number>1</number>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <widget class="QFrame" name="SendCoinsInsecure">
<property name="spacing"> <property name="windowTitle">
<number>12</number> <string>Form</string>
</property> </property>
<item row="5" column="0"> <property name="frameShape">
<widget class="QLabel" name="label"> <enum>QFrame::StyledPanel</enum>
<property name="text"> </property>
<string>A&amp;mount:</string> <property name="frameShadow">
</property> <enum>QFrame::Sunken</enum>
<property name="alignment"> </property>
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <layout class="QGridLayout" name="gridLayout">
</property> <property name="spacing">
<property name="buddy"> <number>12</number>
<cstring>payAmount</cstring> </property>
</property> <item row="5" column="0">
</widget> <widget class="QLabel" name="label">
</item> <property name="text">
<item row="3" column="0"> <string>A&amp;mount:</string>
<widget class="QLabel" name="label_2"> </property>
<property name="text"> <property name="alignment">
<string>Pay &amp;To:</string> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="alignment"> <property name="buddy">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <cstring>payAmount</cstring>
</property> </property>
<property name="buddy"> </widget>
<cstring>payTo</cstring> </item>
</property> <item row="3" column="0">
</widget> <widget class="QLabel" name="label_2">
</item> <property name="text">
<item row="5" column="1"> <string>Pay &amp;To:</string>
<widget class="BitcoinAmountField" name="payAmount"/> </property>
</item> <property name="alignment">
<item row="4" column="0"> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<widget class="QLabel" name="label_4"> </property>
<property name="text"> <property name="buddy">
<string>&amp;Label:</string> <cstring>payTo</cstring>
</property> </property>
<property name="alignment"> </widget>
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </item>
</property> <item row="5" column="1">
<property name="buddy"> <widget class="BitcoinAmountField" name="payAmount"/>
<cstring>addAsLabel</cstring> </item>
</property> <item row="4" column="0">
</widget> <widget class="QLabel" name="label_4">
</item> <property name="text">
<item row="3" column="1"> <string>&amp;Label:</string>
<layout class="QHBoxLayout" name="payToLayout"> </property>
<property name="spacing"> <property name="alignment">
<number>0</number> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<item> <property name="buddy">
<widget class="QValidatedLineEdit" name="payTo"> <cstring>addAsLabel</cstring>
<property name="toolTip"> </property>
<string>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</string> </widget>
</property> </item>
<property name="maxLength"> <item row="3" column="1">
<number>34</number> <layout class="QHBoxLayout" name="payToLayout">
</property> <property name="spacing">
</widget> <number>0</number>
</item> </property>
<item> <item>
<widget class="QToolButton" name="addressBookButton"> <widget class="QValidatedLineEdit" name="payTo">
<property name="toolTip"> <property name="toolTip">
<string>Choose address from address book</string> <string>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</string>
</property> </property>
<property name="text"> <property name="maxLength">
<string/> <number>34</number>
</property> </property>
<property name="icon"> </widget>
<iconset resource="../bitcoin.qrc"> </item>
<normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset> <item>
</property> <widget class="QToolButton" name="addressBookButton">
<property name="shortcut"> <property name="toolTip">
<string>Alt+A</string> <string>Choose address from address book</string>
</property> </property>
</widget> <property name="text">
</item> <string/>
<item> </property>
<widget class="QToolButton" name="pasteButton"> <property name="icon">
<property name="toolTip"> <iconset resource="../bitcoin.qrc">
<string>Paste address from clipboard</string> <normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset>
</property> </property>
<property name="text"> <property name="shortcut">
<string/> <string>Alt+A</string>
</property> </property>
<property name="icon"> </widget>
<iconset resource="../bitcoin.qrc"> </item>
<normaloff>:/icons/editpaste</normaloff>:/icons/editpaste</iconset> <item>
</property> <widget class="QToolButton" name="pasteButton">
<property name="shortcut"> <property name="toolTip">
<string>Alt+P</string> <string>Paste address from clipboard</string>
</property> </property>
</widget> <property name="text">
</item> <string/>
<item> </property>
<widget class="QToolButton" name="deleteButton"> <property name="icon">
<property name="toolTip"> <iconset resource="../bitcoin.qrc">
<string>Remove this recipient</string> <normaloff>:/icons/editpaste</normaloff>:/icons/editpaste</iconset>
</property> </property>
<property name="text"> <property name="shortcut">
<string/> <string>Alt+P</string>
</property> </property>
<property name="icon"> </widget>
<iconset resource="../bitcoin.qrc"> </item>
<normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> <item>
</property> <widget class="QToolButton" name="deleteButton">
</widget> <property name="toolTip">
</item> <string>Remove this recipient</string>
</layout> </property>
</item> <property name="text">
<item row="4" column="1"> <string/>
<widget class="QValidatedLineEdit" name="addAsLabel"> </property>
<property name="toolTip"> <property name="icon">
<string>Enter a label for this address to add it to your address book</string> <iconset resource="../bitcoin.qrc">
</property> <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset>
</widget> </property>
</item> </widget>
</layout> </item>
</layout>
</item>
<item row="4" column="1">
<widget class="QValidatedLineEdit" name="addAsLabel">
<property name="toolTip">
<string>Enter a label for this address to add it to your address book</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="SendCoinsSecure">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>230</red>
<green>255</green>
<blue>224</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>185</red>
<green>243</green>
<blue>171</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>93</red>
<green>155</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>155</red>
<green>255</green>
<blue>147</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>119</red>
<green>255</green>
<blue>233</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>197</red>
<green>243</green>
<blue>187</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>125</red>
<green>194</green>
<blue>122</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>230</red>
<green>255</green>
<blue>224</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>185</red>
<green>243</green>
<blue>171</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>93</red>
<green>155</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>155</red>
<green>255</green>
<blue>147</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>119</red>
<green>255</green>
<blue>233</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>197</red>
<green>243</green>
<blue>187</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>125</red>
<green>194</green>
<blue>122</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>230</red>
<green>255</green>
<blue>224</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>185</red>
<green>243</green>
<blue>171</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>93</red>
<green>155</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="BrightText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>155</red>
<green>255</green>
<blue>147</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>70</red>
<green>116</green>
<blue>59</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>140</red>
<green>232</green>
<blue>119</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>125</red>
<green>194</green>
<blue>122</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>220</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="windowTitle">
<string>SecureSend</string>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_s">
<property name="spacing">
<number>12</number>
</property>
<item row="4" column="0">
<widget class="QLabel" name="label_s4">
<property name="text">
<string>Memo:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>addAsLabel</cstring>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_s1">
<property name="text">
<string>A&amp;mount:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payAmount</cstring>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_s2">
<property name="text">
<string>Pay &amp;To:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>payTo_s</cstring>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="BitcoinAmountField" name="payAmount_s">
<property name="enabled">
<bool>false</bool>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="2">
<layout class="QHBoxLayout" name="payToLayout_s">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="payTo_s">
</widget>
</item>
</layout>
</item>
<item row="4" column="2">
<widget class="QLabel" name="memo_s">
<property name="text">
<string>message from merchant</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

3
src/qt/guiconstants.h

@ -28,6 +28,9 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80;
/* Maximum allowed URI length */ /* Maximum allowed URI length */
static const int MAX_URI_LENGTH = 255; static const int MAX_URI_LENGTH = 255;
/* Maximum somewhat-sane size of a payment request file */
static const int MAX_PAYMENT_REQUEST_SIZE = 50000; // bytes
/* QRCodeDialog -- size of exported QR Code image */ /* QRCodeDialog -- size of exported QR Code image */
#define EXPORT_IMAGE_SIZE 256 #define EXPORT_IMAGE_SIZE 256

8
src/qt/guiutil.cpp

@ -148,6 +148,14 @@ bool parseBitcoinURI(QString uri, SendCoinsRecipient *out)
return parseBitcoinURI(uriInstance, out); return parseBitcoinURI(uriInstance, out);
} }
bool isDust(const QString& address, qint64 amount)
{
CTxDestination dest = CBitcoinAddress(address.toStdString()).Get();
CScript script; script.SetDestination(dest);
CTxOut txOut(amount, script);
return txOut.IsDust(CTransaction::nMinRelayTxFee);
}
QString HtmlEscape(const QString& str, bool fMultiLine) QString HtmlEscape(const QString& str, bool fMultiLine)
{ {
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000

3
src/qt/guiutil.h

@ -36,6 +36,9 @@ namespace GUIUtil
bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out);
bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); bool parseBitcoinURI(QString uri, SendCoinsRecipient *out);
// Returns true if given address+amount meets "dust" definition
bool isDust(const QString& address, qint64 amount);
// HTML escaping for rich text controls // HTML escaping for rich text controls
QString HtmlEscape(const QString& str, bool fMultiLine=false); QString HtmlEscape(const QString& str, bool fMultiLine=false);
QString HtmlEscape(const std::string& str, bool fMultiLine=false); QString HtmlEscape(const std::string& str, bool fMultiLine=false);

11
src/qt/optionsmodel.cpp

@ -290,3 +290,14 @@ qint64 OptionsModel::getTransactionFee()
{ {
return nTransactionFee; return nTransactionFee;
} }
bool OptionsModel::getProxySettings(QString& proxyIP, quint16 &proxyPort) const
{
std::string proxy = GetArg("-proxy", "");
if (proxy.empty()) return false;
CService addrProxy(proxy);
proxyIP = QString(addrProxy.ToStringIP().c_str());
proxyPort = addrProxy.GetPort();
return true;
}

1
src/qt/optionsmodel.h

@ -49,6 +49,7 @@ public:
int getDisplayUnit() { return nDisplayUnit; } int getDisplayUnit() { return nDisplayUnit; }
bool getDisplayAddresses() { return bDisplayAddresses; } bool getDisplayAddresses() { return bDisplayAddresses; }
QString getLanguage() { return language; } QString getLanguage() { return language; }
bool getProxySettings(QString& proxyIP, quint16 &proxyPort) const;
private: private:
int nDisplayUnit; int nDisplayUnit;

46
src/qt/paymentrequest.proto

@ -0,0 +1,46 @@
//
// Simple Bitcoin Payment Protocol messages
//
// Use fields 100+ for extensions;
// to avoid conflicts, register extensions at:
// https://en.bitcoin.it/wiki/Payment_Request
//
package payments;
option java_package = "org.bitcoin.protocols.payments";
option java_outer_classname = "Protos";
// Generalized form of "send payment to this/these bitcoin addresses"
message Output {
optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis
required bytes script = 2; // usually one of the standard Script forms
}
message PaymentDetails {
optional string network = 1 [default = "main"]; // "main" or "test"
repeated Output outputs = 2; // Where payment should be sent
required uint64 time = 3; // Timestamp; when payment request created
optional uint64 expires = 4; // Timestamp; when this request should be considered invalid
optional string memo = 5; // Human-readable description of request for the customer
optional string payment_url = 6; // URL to send Payment and get PaymentACK
optional bytes merchant_data = 7; // Arbitrary data to include in the Payment message
}
message PaymentRequest {
optional uint32 payment_details_version = 1 [default = 1];
optional string pki_type = 2 [default = "none"]; // none / x509+sha256 / x509+sha1
optional bytes pki_data = 3; // depends on pki_type
required bytes serialized_payment_details = 4; // PaymentDetails
optional bytes signature = 5; // pki-dependent signature
}
message X509Certificates {
repeated bytes certificate = 1; // DER-encoded X.509 certificate chain
}
message Payment {
optional bytes merchant_data = 1; // From PaymentDetails.merchant_data
repeated bytes transactions = 2; // Signed transactions that satisfy PaymentDetails.outputs
repeated Output refund_to = 3; // Where to send refunds, if a refund is necessary
optional string memo = 4; // Human-readable message for the merchant
}
message PaymentACK {
required Payment payment = 1; // Payment message that triggered this ACK
optional string memo = 2; // human-readable message for customer
}

204
src/qt/paymentrequestplus.cpp

@ -0,0 +1,204 @@
//
// Wraps dumb protocol buffer paymentRequest
// with some extra methods
//
#include <QDateTime>
#include <QDebug>
#include <QSslCertificate>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include <stdexcept>
#include "paymentrequestplus.h"
class SSLVerifyError : public std::runtime_error
{
public:
SSLVerifyError(std::string err) : std::runtime_error(err) { }
};
bool PaymentRequestPlus::parse(const QByteArray& data)
{
bool parseOK = paymentRequest.ParseFromArray(data.data(), data.size());
if (!parseOK) {
qDebug() << "Error parsing payment request";
return false;
}
if (paymentRequest.payment_details_version() > 1) {
qDebug() << "Received up-version payment details, version=" << paymentRequest.payment_details_version();
return false;
}
parseOK = details.ParseFromString(paymentRequest.serialized_payment_details());
if (!parseOK)
{
qDebug() << "Error parsing payment details";
paymentRequest.Clear();
return false;
}
return true;
}
bool PaymentRequestPlus::SerializeToString(string* output) const
{
return paymentRequest.SerializeToString(output);
}
bool PaymentRequestPlus::IsInitialized() const
{
return paymentRequest.IsInitialized();
}
QString PaymentRequestPlus::getPKIType() const
{
if (!IsInitialized()) return QString("none");
return QString::fromStdString(paymentRequest.pki_type());
}
bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) const
{
merchant.clear();
if (!IsInitialized())
return false;
// One day we'll support more PKI types, but just
// x509 for now:
const EVP_MD* digestAlgorithm = NULL;
if (paymentRequest.pki_type() == "x509+sha256") {
digestAlgorithm = EVP_sha256();
}
else if (paymentRequest.pki_type() == "x509+sha1") {
digestAlgorithm = EVP_sha1();
}
else if (paymentRequest.pki_type() == "none") {
if (fDebug) qDebug() << "PaymentRequest: pki_type == none";
return false;
}
else {
qDebug() << "PaymentRequest: unknown pki_type " << paymentRequest.pki_type().c_str();
return false;
}
payments::X509Certificates certChain;
if (!certChain.ParseFromString(paymentRequest.pki_data())) {
qDebug() << "PaymentRequest: error parsing pki_data";
return false;
}
std::vector<X509*> certs;
const QDateTime currentTime = QDateTime::currentDateTime();
for (int i = 0; i < certChain.certificate_size(); i++) {
QByteArray certData(certChain.certificate(i).data(), certChain.certificate(i).size());
QSslCertificate qCert(certData, QSsl::Der);
if (currentTime < qCert.effectiveDate() || currentTime > qCert.expiryDate()) {
qDebug() << "PaymentRequest: certificate expired or not yet active: " << qCert;
return false;
}
#if QT_VERSION >= 0x050000
if (qCert.isBlacklisted()) {
qDebug() << "PaymentRequest: certificate blacklisted: " << qCert;
return false;
}
#endif
const unsigned char *data = (const unsigned char *)certChain.certificate(i).data();
X509 *cert = d2i_X509(NULL, &data, certChain.certificate(i).size());
if (cert)
certs.push_back(cert);
}
if (certs.empty()) {
qDebug() << "PaymentRequest: empty certificate chain";
return false;
}
// The first cert is the signing cert, the rest are untrusted certs that chain
// to a valid root authority. OpenSSL needs them separately.
STACK_OF(X509) *chain = sk_X509_new_null();
for (int i = certs.size()-1; i > 0; i--) {
sk_X509_push(chain, certs[i]);
}
X509 *signing_cert = certs[0];
// Now create a "store context", which is a single use object for checking,
// load the signing cert into it and verify.
X509_STORE_CTX *store_ctx = X509_STORE_CTX_new();
if (!store_ctx) {
qDebug() << "PaymentRequest: error creating X509_STORE_CTX";
return false;
}
char *website = NULL;
bool fResult = true;
try
{
if (!X509_STORE_CTX_init(store_ctx, certStore, signing_cert, chain))
{
int error = X509_STORE_CTX_get_error(store_ctx);
throw SSLVerifyError(X509_verify_cert_error_string(error));
}
// Now do the verification!
int result = X509_verify_cert(store_ctx);
if (result != 1) {
int error = X509_STORE_CTX_get_error(store_ctx);
throw SSLVerifyError(X509_verify_cert_error_string(error));
}
X509_NAME *certname = X509_get_subject_name(signing_cert);
// Valid cert; check signature:
payments::PaymentRequest rcopy(paymentRequest); // Copy
rcopy.set_signature(std::string(""));
std::string data_to_verify; // Everything but the signature
rcopy.SerializeToString(&data_to_verify);
EVP_MD_CTX ctx;
EVP_PKEY *pubkey = X509_get_pubkey(signing_cert);
EVP_MD_CTX_init(&ctx);
if (!EVP_VerifyInit_ex(&ctx, digestAlgorithm, NULL) ||
!EVP_VerifyUpdate(&ctx, data_to_verify.data(), data_to_verify.size()) ||
!EVP_VerifyFinal(&ctx, (const unsigned char*)paymentRequest.signature().data(), paymentRequest.signature().size(), pubkey)) {
throw SSLVerifyError("Bad signature, invalid PaymentRequest.");
}
// OpenSSL API for getting human printable strings from certs is baroque.
int textlen = X509_NAME_get_text_by_NID(certname, NID_commonName, NULL, 0);
website = new char[textlen + 1];
if (X509_NAME_get_text_by_NID(certname, NID_commonName, website, textlen + 1) == textlen && textlen > 0) {
merchant = website;
}
else {
throw SSLVerifyError("Bad certificate, missing common name");
}
// TODO: detect EV certificates and set merchant = business name instead of unfriendly NID_commonName ?
}
catch (SSLVerifyError& err)
{
fResult = false;
qDebug() << "PaymentRequestPlus::getMerchant SSL err: " << err.what();
}
if (website)
delete[] website;
X509_STORE_CTX_free(store_ctx);
for (unsigned int i = 0; i < certs.size(); i++)
X509_free(certs[i]);
return fResult;
}
QList<std::pair<CScript,qint64> > PaymentRequestPlus::getPayTo() const
{
QList<std::pair<CScript,qint64> > result;
for (int i = 0; i < details.outputs_size(); i++)
{
const unsigned char* scriptStr = (const unsigned char*)details.outputs(i).script().data();
CScript s(scriptStr, scriptStr+details.outputs(i).script().size());
result.append(make_pair(s, details.outputs(i).amount()));
}
return result;
}

41
src/qt/paymentrequestplus.h

@ -0,0 +1,41 @@
#ifndef PAYMENTREQUESTPLUS_H
#define PAYMENTREQUESTPLUS_H
#include <QByteArray>
#include <QList>
#include <QString>
#include "base58.h"
#include "paymentrequest.pb.h"
//
// Wraps dumb protocol buffer paymentRequest
// with extra methods
//
class PaymentRequestPlus
{
public:
PaymentRequestPlus() { }
bool parse(const QByteArray& data);
bool SerializeToString(string* output) const;
bool IsInitialized() const;
QString getPKIType() const;
// Returns true if merchant's identity is authenticated, and
// returns human-readable merchant identity in merchant
bool getMerchant(X509_STORE* certStore, QString& merchant) const;
// Returns list of outputs, amount
QList<std::pair<CScript,qint64> > getPayTo() const;
const payments::PaymentDetails& getDetails() const { return details; }
private:
payments::PaymentRequest paymentRequest;
payments::PaymentDetails details;
};
#endif // PAYMENTREQUESTPLUS_H

527
src/qt/paymentserver.cpp

@ -2,30 +2,63 @@
// Distributed under the MIT/X11 software license, see the accompanying // Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "paymentserver.h"
#include "guiconstants.h"
#include "ui_interface.h"
#include "util.h"
#include <QApplication> #include <QApplication>
#include <QByteArray> #include <QByteArray>
#include <QDataStream> #include <QDataStream>
#include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QFile>
#include <QFileOpenEvent> #include <QFileOpenEvent>
#include <QHash> #include <QHash>
#include <QList>
#include <QLocalServer> #include <QLocalServer>
#include <QLocalSocket> #include <QLocalSocket>
#include <QStringList> #include <QStringList>
#include <QTextDocument>
#include <QNetworkAccessManager>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSslCertificate>
#include <QSslError>
#include <QSslSocket>
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000
#include <QUrl> #include <QUrl>
#else
#include <QUrlQuery>
#endif #endif
#include <cstdlib>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include "base58.h"
#include "bitcoinunits.h"
#include "guiconstants.h"
#include "guiutil.h"
#include "optionsmodel.h"
#include "paymentserver.h"
#include "ui_interface.h"
#include "util.h"
#include "wallet.h"
#include "walletmodel.h"
using namespace boost; using namespace boost;
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
const QString BITCOIN_IPC_PREFIX("bitcoin:"); const QString BITCOIN_IPC_PREFIX("bitcoin:");
X509_STORE* PaymentServer::certStore = NULL;
void PaymentServer::freeCertStore()
{
if (PaymentServer::certStore != NULL)
{
X509_STORE_free(PaymentServer::certStore);
PaymentServer::certStore = NULL;
}
}
// //
// Create a name that is unique for: // Create a name that is unique for:
// testnet / non-testnet // testnet / non-testnet
@ -45,11 +78,99 @@ static QString ipcServerName()
} }
// //
// This stores payment requests received before // We store payment URLs and requests received before
// the main GUI window is up and ready to ask the user // the main GUI window is up and ready to ask the user
// to send payment. // to send payment.
static QList<QString> savedPaymentRequests;
static void ReportInvalidCertificate(const QSslCertificate& cert)
{
if (fDebug) {
qDebug() << "Invalid certificate: " << cert.subjectInfo(QSslCertificate::CommonName);
}
}
//
// Load openSSL's list of root certificate authorities
// //
static QStringList savedPaymentRequests; void PaymentServer::LoadRootCAs(X509_STORE* _store)
{
if (PaymentServer::certStore == NULL)
atexit(PaymentServer::freeCertStore);
else
freeCertStore();
// Unit tests mostly use this, to pass in fake root CAs:
if (_store)
{
PaymentServer::certStore = _store;
return;
}
// Normal execution, use either -rootcertificates or system certs:
PaymentServer::certStore = X509_STORE_new();
// Note: use "-system-" default here so that users can pass -rootcertificates=""
// and get 'I don't like X.509 certificates, don't trust anybody' behavior:
QString certFile = QString::fromStdString(GetArg("-rootcertificates", "-system-"));
if (certFile.isEmpty())
return; // Empty store
QList<QSslCertificate> certList;
if (certFile != "-system-")
{
certList = QSslCertificate::fromPath(certFile);
// Use those certificates when fetching payment requests, too:
QSslSocket::setDefaultCaCertificates(certList);
}
else
certList = QSslSocket::systemCaCertificates ();
int nRootCerts = 0;
const QDateTime currentTime = QDateTime::currentDateTime();
foreach (const QSslCertificate& cert, certList)
{
if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) {
ReportInvalidCertificate(cert);
continue;
}
#if QT_VERSION >= 0x050000
if (cert.isBlacklisted()) {
ReportInvalidCertificate(cert);
continue;
}
#endif
QByteArray certData = cert.toDer();
const unsigned char *data = (const unsigned char *)certData.data();
X509* x509 = d2i_X509(0, &data, certData.size());
if (x509 && X509_STORE_add_cert( PaymentServer::certStore, x509))
{
// Note: X509_STORE_free will free the X509* objects when
// the PaymentServer is destroyed
++nRootCerts;
}
else
{
ReportInvalidCertificate(cert);
continue;
}
}
if (fDebug)
qDebug() << "PaymentServer: loaded " << nRootCerts << " root certificates";
// Project for another day:
// Fetch certificate revocation lists, and add them to certStore.
// Issues to consider:
// performance (start a thread to fetch in background?)
// privacy (fetch through tor/proxy so IP address isn't revealed)
// would it be easier to just use a compiled-in blacklist?
// or use Qt's blacklist?
// "certificate stapling" with server-side caching is more efficient
}
// //
// Sending to the server is done synchronously, at startup. // Sending to the server is done synchronously, at startup.
@ -57,19 +178,54 @@ static QStringList savedPaymentRequests;
// and the items in savedPaymentRequest will be handled // and the items in savedPaymentRequest will be handled
// when uiReady() is called. // when uiReady() is called.
// //
bool PaymentServer::ipcSendCommandLine() bool PaymentServer::ipcSendCommandLine(int argc, char* argv[])
{ {
bool fResult = false; bool fResult = false;
const QStringList& args = qApp->arguments(); for (int i = 1; i < argc; i++)
for (int i = 1; i < args.size(); i++)
{ {
if (!args[i].startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) QString arg(argv[i]);
if (arg.startsWith("-"))
continue; continue;
savedPaymentRequests.append(args[i]);
if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin:
{
savedPaymentRequests.append(arg);
SendCoinsRecipient r;
if (GUIUtil::parseBitcoinURI(arg, &r))
{
CBitcoinAddress address(r.address.toStdString());
SelectParams(CChainParams::MAIN);
if (!address.IsValid())
{
SelectParams(CChainParams::TESTNET);
}
}
}
else if (QFile::exists(arg)) // Filename
{
savedPaymentRequests.append(arg);
PaymentRequestPlus request;
if (readPaymentRequest(arg, request))
{
if (request.getDetails().network() == "main")
SelectParams(CChainParams::MAIN);
else
SelectParams(CChainParams::TESTNET);
}
}
else
{
qDebug() << "Payment request file does not exist: " << argv[i];
// Printing to debug.log is about the best we can do here, the
// GUI hasn't started yet so we can't pop up a message box.
}
} }
foreach (const QString& arg, savedPaymentRequests) foreach (const QString& r, savedPaymentRequests)
{ {
QLocalSocket* socket = new QLocalSocket(); QLocalSocket* socket = new QLocalSocket();
socket->connectToServer(ipcServerName(), QIODevice::WriteOnly); socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
@ -79,7 +235,7 @@ bool PaymentServer::ipcSendCommandLine()
QByteArray block; QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly); QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0); out.setVersion(QDataStream::Qt_4_0);
out << arg; out << r;
out.device()->seek(0); out.device()->seek(0);
socket->write(block); socket->write(block);
socket->flush(); socket->flush();
@ -92,50 +248,148 @@ bool PaymentServer::ipcSendCommandLine()
return fResult; return fResult;
} }
PaymentServer::PaymentServer(QApplication* parent) : QObject(parent), saveURIs(true) PaymentServer::PaymentServer(QObject* parent,
bool startLocalServer) : QObject(parent), saveURIs(true)
{ {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
// Install global event filter to catch QFileOpenEvents on the mac (sent when you click bitcoin: links) // Install global event filter to catch QFileOpenEvents on the mac (sent when you click bitcoin: links)
parent->installEventFilter(this); if (parent)
parent->installEventFilter(this);
QString name = ipcServerName(); QString name = ipcServerName();
// Clean up old socket leftover from a crash: // Clean up old socket leftover from a crash:
QLocalServer::removeServer(name); QLocalServer::removeServer(name);
uriServer = new QLocalServer(this); if (startLocalServer)
{
uriServer = new QLocalServer(this);
if (!uriServer->listen(name)) if (!uriServer->listen(name))
qDebug() << tr("Cannot start bitcoin: click-to-pay handler"); qDebug() << "Cannot start bitcoin: click-to-pay handler";
else else
connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection())); connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
}
// netManager is null until uiReady() is called
netManager = NULL;
} }
bool PaymentServer::eventFilter(QObject *object, QEvent *event) PaymentServer::~PaymentServer()
{
google::protobuf::ShutdownProtobufLibrary();
}
//
// OSX-specific way of handling bitcoin uris and
// PaymentRequest mime types
//
bool PaymentServer::eventFilter(QObject *, QEvent *event)
{ {
// clicking on bitcoin: URLs creates FileOpen events on the Mac: // clicking on bitcoin: URLs creates FileOpen events on the Mac:
if (event->type() == QEvent::FileOpen) if (event->type() == QEvent::FileOpen)
{ {
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event); QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
if (!fileEvent->url().isEmpty()) if (!fileEvent->file().isEmpty())
{ handleURIOrFile(fileEvent->file());
if (saveURIs) // Before main window is ready: else if (!fileEvent->url().isEmpty())
savedPaymentRequests.append(fileEvent->url().toString()); handleURIOrFile(fileEvent->url().toString());
else
emit receivedURI(fileEvent->url().toString()); return true;
return true;
}
} }
return false; return false;
} }
void PaymentServer::initNetManager(const OptionsModel& options)
{
if (netManager != NULL)
delete netManager;
// netManager is used to fetch paymentrequests given in bitcoin: URI's
netManager = new QNetworkAccessManager(this);
// Use proxy settings from options:
QString proxyIP;
quint16 proxyPort;
if (options.getProxySettings(proxyIP, proxyPort))
{
QNetworkProxy proxy;
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(proxyIP);
proxy.setPort(proxyPort);
netManager->setProxy(proxy);
}
connect(netManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(netRequestFinished(QNetworkReply*)));
connect(netManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> &)),
this, SLOT(reportSslErrors(QNetworkReply*, const QList<QSslError> &)));
}
void PaymentServer::uiReady() void PaymentServer::uiReady()
{ {
assert(netManager != NULL); // Must call initNetManager before uiReady()
saveURIs = false; saveURIs = false;
foreach (const QString& s, savedPaymentRequests) foreach (const QString& s, savedPaymentRequests)
emit receivedURI(s); {
handleURIOrFile(s);
}
savedPaymentRequests.clear(); savedPaymentRequests.clear();
} }
void PaymentServer::handleURIOrFile(const QString& s)
{
if (saveURIs)
{
savedPaymentRequests.append(s);
return;
}
if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin:
{
#if QT_VERSION >= 0x050000
QUrlQuery url((QUrl(s)));
#else
QUrl url(s);
#endif
if (url.hasQueryItem("request"))
{
QByteArray temp; temp.append(url.queryItemValue("request"));
QString decoded = QUrl::fromPercentEncoding(temp);
QUrl fetchUrl(decoded, QUrl::StrictMode);
if (fDebug) qDebug() << "PaymentServer::fetchRequest " << fetchUrl;
if (fetchUrl.isValid())
fetchRequest(fetchUrl);
else
qDebug() << "PaymentServer: invalid url: " << fetchUrl;
return;
}
SendCoinsRecipient recipient;
if (GUIUtil::parseBitcoinURI(s, &recipient))
emit receivedPaymentRequest(recipient);
return;
}
if (QFile::exists(s))
{
PaymentRequestPlus request;
QList<SendCoinsRecipient> recipients;
if (readPaymentRequest(s, request) && processPaymentRequest(request, recipients)) {
foreach (const SendCoinsRecipient& recipient, recipients){
emit receivedPaymentRequest(recipient);
}
}
return;
}
}
void PaymentServer::handleURIConnection() void PaymentServer::handleURIConnection()
{ {
QLocalSocket *clientConnection = uriServer->nextPendingConnection(); QLocalSocket *clientConnection = uriServer->nextPendingConnection();
@ -154,8 +408,209 @@ void PaymentServer::handleURIConnection()
QString message; QString message;
in >> message; in >> message;
if (saveURIs) handleURIOrFile(message);
savedPaymentRequests.append(message); }
else
emit receivedURI(message); bool PaymentServer::readPaymentRequest(const QString& filename, PaymentRequestPlus& request)
{
QFile f(filename);
if (!f.open(QIODevice::ReadOnly))
{
qDebug() << "PaymentServer::readPaymentRequest fail to open " << filename;
return false;
}
if (f.size() > MAX_PAYMENT_REQUEST_SIZE)
{
qDebug() << "PaymentServer::readPaymentRequest " << filename << " too large";
return false;
}
QByteArray data = f.readAll();
return request.parse(data);
}
bool
PaymentServer::processPaymentRequest(PaymentRequestPlus& request,
QList<SendCoinsRecipient>& recipients)
{
QList<std::pair<CScript,qint64> > sendingTos = request.getPayTo();
qint64 totalAmount = 0;
foreach(const PAIRTYPE(CScript, qint64)& sendingTo, sendingTos) {
CTxOut txOut(sendingTo.second, sendingTo.first);
if (txOut.IsDust(CTransaction::nMinRelayTxFee)) {
QString message = QObject::tr("Requested payment amount (%1) too small")
.arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, sendingTo.second));
qDebug() << message;
emit reportError(tr("Payment request error"), message, CClientUIInterface::MODAL);
return false;
}
totalAmount += sendingTo.second;
}
recipients.append(SendCoinsRecipient());
if (request.getMerchant(PaymentServer::certStore, recipients[0].authenticatedMerchant)) {
recipients[0].paymentRequest = request;
recipients[0].amount = totalAmount;
if (fDebug) qDebug() << "PaymentRequest from " << recipients[0].authenticatedMerchant;
}
else {
recipients.clear();
// Insecure payment requests may turn into more than one recipient if
// the merchant is requesting payment to more than one address.
for (int i = 0; i < sendingTos.size(); i++) {
std::pair<CScript, qint64>& sendingTo = sendingTos[i];
recipients.append(SendCoinsRecipient());
recipients[i].amount = sendingTo.second;
QString memo = QString::fromStdString(request.getDetails().memo());
#if QT_VERSION < 0x050000
recipients[i].label = Qt::escape(memo);
#else
recipients[i].label = memo.toHtmlEscaped();
#endif
CTxDestination dest;
if (ExtractDestination(sendingTo.first, dest)) {
if (i == 0) // Tie request to first pay-to, we don't want multiple ACKs
recipients[i].paymentRequest = request;
recipients[i].address = QString::fromStdString(CBitcoinAddress(dest).ToString());
if (fDebug) qDebug() << "PaymentRequest, insecure " << recipients[i].address;
}
else {
// Insecure payments to custom bitcoin addresses are not supported
// (there is no good way to tell the user where they are paying in a way
// they'd have a chance of understanding).
emit reportError(tr("Payment request error"),
tr("Insecure requests to custom payment scripts unsupported"),
CClientUIInterface::MODAL);
return false;
}
}
}
return true;
}
void
PaymentServer::fetchRequest(const QUrl& url)
{
QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, "PaymentRequest");
netRequest.setUrl(url);
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
netManager->get(netRequest);
}
void
PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction)
{
const payments::PaymentDetails& details = recipient.paymentRequest.getDetails();
if (!details.has_payment_url())
return;
QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, "PaymentACK");
netRequest.setUrl(QString::fromStdString(details.payment_url()));
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/bitcoin-payment");
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
payments::Payment payment;
payment.set_merchant_data(details.merchant_data());
payment.add_transactions(transaction.data(), transaction.size());
// Create a new refund address, or re-use:
QString account = tr("Refund from") + QString(" ") + recipient.authenticatedMerchant;
std::string strAccount = account.toStdString();
set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
if (!refundAddresses.empty()) {
CScript s; s.SetDestination(*refundAddresses.begin());
payments::Output* refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
}
else {
CPubKey newKey;
if (wallet->GetKeyFromPool(newKey, false)) {
CKeyID keyID = newKey.GetID();
wallet->SetAddressBook(keyID, strAccount, "refund");
CScript s; s.SetDestination(keyID);
payments::Output* refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
}
else {
// This should never happen, because sending coins should have just unlocked the wallet
// and refilled the keypool
qDebug() << "Error getting refund key, refund_to not set";
}
}
int length = payment.ByteSize();
netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
QByteArray serData(length, '\0');
if (payment.SerializeToArray(serData.data(), length)) {
netManager->post(netRequest, serData);
}
else {
// This should never happen, either:
qDebug() << "Error serializing payment message";
}
}
void
PaymentServer::netRequestFinished(QNetworkReply* reply)
{
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError)
{
QString message = QObject::tr("Error communicating with %1: %2")
.arg(reply->request().url().toString())
.arg(reply->errorString());
qDebug() << message;
emit reportError(tr("Network request error"), message, CClientUIInterface::MODAL);
return;
}
QByteArray data = reply->readAll();
QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
if (requestType == "PaymentRequest")
{
PaymentRequestPlus request;
QList<SendCoinsRecipient> recipients;
if (request.parse(data) && processPaymentRequest(request, recipients)) {
foreach (const SendCoinsRecipient& recipient, recipients){
emit receivedPaymentRequest(recipient);
}
}
else
qDebug() << "PaymentServer::netRequestFinished: error processing PaymentRequest";
return;
}
else if (requestType == "PaymentACK")
{
payments::PaymentACK paymentACK;
if (!paymentACK.ParseFromArray(data.data(), data.size()))
{
QString message = QObject::tr("Bad response from server %1")
.arg(reply->request().url().toString());
qDebug() << message;
emit reportError(tr("Network request error"), message, CClientUIInterface::MODAL);
}
else {
emit receivedPaymentACK(QString::fromStdString(paymentACK.memo()));
}
}
}
void
PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> &errs)
{
QString errString;
foreach (const QSslError& err, errs) {
qDebug() << err;
errString += err.errorString() + "\n";
}
emit reportError(tr("Network request error"), errString, CClientUIInterface::MODAL);
} }

64
src/qt/paymentserver.h

@ -31,37 +31,87 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include "paymentrequestplus.h"
#include "walletmodel.h"
class CWallet;
class OptionsModel;
class QApplication; class QApplication;
class QByteArray;
class QLocalServer; class QLocalServer;
class QNetworkAccessManager;
class QNetworkReply;
class QSslError;
class QUrl;
class PaymentServer : public QObject class PaymentServer : public QObject
{ {
Q_OBJECT Q_OBJECT
private:
bool saveURIs;
QLocalServer* uriServer;
public: public:
// Returns true if there were URIs on the command line // Returns true if there were URIs on the command line
// which were successfully sent to an already-running // which were successfully sent to an already-running
// process. // process.
static bool ipcSendCommandLine(); // Note: if a payment request is given, SelectParams(MAIN/TESTNET)
// will be called so we startup in the right mode.
static bool ipcSendCommandLine(int argc, char *argv[]);
PaymentServer(QApplication* parent); PaymentServer(QObject* parent, // parent should be QApplication object
bool startLocalServer=true);
~PaymentServer();
// Load root certificate authorities. Pass NULL (default)
// to read from the file specified in the -rootcertificates setting,
// or, if that's not set, to use the system default root certificates.
// If you pass in a store, you should not X509_STORE_free it: it will be
// freed either at exit or when another set of CAs are loaded.
static void LoadRootCAs(X509_STORE* store=NULL);
// Return certificate store
static X509_STORE* getCertStore() { return certStore; }
// Setup networking (options is used to get proxy settings)
void initNetManager(const OptionsModel& options);
// Constructor registers this on the parent QApplication to
// receive QEvent::FileOpen events
bool eventFilter(QObject *object, QEvent *event); bool eventFilter(QObject *object, QEvent *event);
signals: signals:
void receivedURI(QString); // Fired when a valid payment request is received
void receivedPaymentRequest(SendCoinsRecipient);
// Fired when a valid PaymentACK is received
void receivedPaymentACK(QString);
// Fired when an error should be reported to the user
void reportError(QString, QString, unsigned int);
public slots: public slots:
// Signal this when the main window's UI is ready // Signal this when the main window's UI is ready
// to display payment requests to the user // to display payment requests to the user
void uiReady(); void uiReady();
// Submit Payment message to a merchant, get back PaymentACK:
void fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction);
private slots: private slots:
void handleURIConnection(); void handleURIConnection();
void netRequestFinished(QNetworkReply*);
void reportSslErrors(QNetworkReply*, const QList<QSslError> &);
private:
static bool readPaymentRequest(const QString& filename, PaymentRequestPlus& request);
bool processPaymentRequest(PaymentRequestPlus& request, QList<SendCoinsRecipient>& recipients);
void handleURIOrFile(const QString& s);
void fetchRequest(const QUrl& url);
bool saveURIs; // true during startup
QLocalServer* uriServer;
static X509_STORE* certStore; // Trusted root certificates
static void freeCertStore();
QNetworkAccessManager* netManager; // Used to fetch payment requests
}; };
#endif // PAYMENTSERVER_H #endif // PAYMENTSERVER_H

47
src/qt/sendcoinsdialog.cpp

@ -93,11 +93,26 @@ void SendCoinsDialog::on_sendButton_clicked()
QStringList formatted; QStringList formatted;
foreach(const SendCoinsRecipient &rcp, recipients) foreach(const SendCoinsRecipient &rcp, recipients)
{ {
QString amount = BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount);
if (rcp.authenticatedMerchant.isEmpty())
{
QString address = rcp.address;
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000
formatted.append(tr("<b>%1</b> to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), Qt::escape(rcp.label), rcp.address)); QString to = Qt::escape(rcp.label);
#else #else
formatted.append(tr("<b>%1</b> to %2 (%3)").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount), rcp.label.toHtmlEscaped(), rcp.address)); QString to = rcp.label.toHtmlEscaped();
#endif #endif
formatted.append(tr("<b>%1</b> to %2 (%3)").arg(amount, to, address));
}
else
{
#if QT_VERSION < 0x050000
QString merchant = Qt::escape(rcp.authenticatedMerchant);
#else
QString merchant = rcp.authenticatedMerchant.toHtmlEscaped();
#endif
formatted.append(tr("<b>%1</b> to %2").arg(amount, merchant));
}
} }
fNewRecipientAllowed = false; fNewRecipientAllowed = false;
@ -292,20 +307,30 @@ void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
entry->setValue(rv); entry->setValue(rv);
} }
bool SendCoinsDialog::handleURI(const QString &uri) bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
{ {
SendCoinsRecipient rv; if (!rv.authenticatedMerchant.isEmpty()) {
// URI has to be valid // Expired payment request?
if (GUIUtil::parseBitcoinURI(uri, &rv)) const payments::PaymentDetails& details = rv.paymentRequest.getDetails();
{ if (details.has_expires() && (int64)details.expires() < GetTime())
{
QMessageBox::warning(this, tr("Send Coins"),
tr("Payment request expired"));
return false;
}
}
else {
CBitcoinAddress address(rv.address.toStdString()); CBitcoinAddress address(rv.address.toStdString());
if (!address.IsValid()) if (!address.IsValid()) {
QString strAddress(address.ToString().c_str());
QMessageBox::warning(this, tr("Send Coins"),
tr("Invalid payment address %1").arg(strAddress));
return false; return false;
pasteEntry(rv); }
return true;
} }
return false; pasteEntry(rv);
return true;
} }
void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance) void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint64 immatureBalance)

3
src/qt/sendcoinsdialog.h

@ -2,6 +2,7 @@
#define SENDCOINSDIALOG_H #define SENDCOINSDIALOG_H
#include <QDialog> #include <QDialog>
#include <QVariant>
namespace Ui { namespace Ui {
class SendCoinsDialog; class SendCoinsDialog;
@ -31,7 +32,7 @@ public:
void setAddress(const QString &address); void setAddress(const QString &address);
void pasteEntry(const SendCoinsRecipient &rv); void pasteEntry(const SendCoinsRecipient &rv);
bool handleURI(const QString &uri); bool handlePaymentRequest(const SendCoinsRecipient &recipient);
public slots: public slots:
void clear(); void clear();

56
src/qt/sendcoinsentry.cpp

@ -12,12 +12,14 @@
#include <QClipboard> #include <QClipboard>
SendCoinsEntry::SendCoinsEntry(QWidget *parent) : SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
QFrame(parent), QStackedWidget(parent),
ui(new Ui::SendCoinsEntry), ui(new Ui::SendCoinsEntry),
model(0) model(0)
{ {
ui->setupUi(this); ui->setupUi(this);
setCurrentWidget(ui->SendCoinsInsecure);
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
ui->payToLayout->setSpacing(4); ui->payToLayout->setSpacing(4);
#endif #endif
@ -101,24 +103,24 @@ bool SendCoinsEntry::validate()
// Check input validity // Check input validity
bool retval = true; bool retval = true;
if(!ui->payAmount->validate()) if (!recipient.authenticatedMerchant.isEmpty())
return retval;
if(!ui->payTo->hasAcceptableInput() ||
(model && !model->validateAddress(ui->payTo->text())))
{ {
ui->payTo->setValid(false);
retval = false; retval = false;
} }
else
if(!ui->payAmount->validate())
{ {
if(ui->payAmount->value() <= 0) retval = false;
{
// Cannot send 0 coins or less
ui->payAmount->setValid(false);
retval = false;
}
} }
if(!ui->payTo->hasAcceptableInput() || // Reject dust outputs:
(model && !model->validateAddress(ui->payTo->text()))) if (retval && GUIUtil::isDust(ui->payTo->text(), ui->payAmount->value())) {
{ ui->payAmount->setValid(false);
ui->payTo->setValid(false);
retval = false; retval = false;
} }
@ -127,13 +129,15 @@ bool SendCoinsEntry::validate()
SendCoinsRecipient SendCoinsEntry::getValue() SendCoinsRecipient SendCoinsEntry::getValue()
{ {
SendCoinsRecipient rv; if (!recipient.authenticatedMerchant.isEmpty())
return recipient;
rv.address = ui->payTo->text(); // User-entered or non-authenticated:
rv.label = ui->addAsLabel->text(); recipient.address = ui->payTo->text();
rv.amount = ui->payAmount->value(); recipient.label = ui->addAsLabel->text();
recipient.amount = ui->payAmount->value();
return rv; return recipient;
} }
QWidget *SendCoinsEntry::setupTabChain(QWidget *prev) QWidget *SendCoinsEntry::setupTabChain(QWidget *prev)
@ -148,9 +152,22 @@ QWidget *SendCoinsEntry::setupTabChain(QWidget *prev)
void SendCoinsEntry::setValue(const SendCoinsRecipient &value) void SendCoinsEntry::setValue(const SendCoinsRecipient &value)
{ {
recipient = value;
ui->payTo->setText(value.address); ui->payTo->setText(value.address);
ui->addAsLabel->setText(value.label); ui->addAsLabel->setText(value.label);
ui->payAmount->setValue(value.amount); ui->payAmount->setValue(value.amount);
if (!recipient.authenticatedMerchant.isEmpty())
{
const payments::PaymentDetails& details = value.paymentRequest.getDetails();
ui->payTo_s->setText(value.authenticatedMerchant);
ui->memo_s->setTextFormat(Qt::PlainText);
ui->memo_s->setText(QString::fromStdString(details.memo()));
ui->payAmount_s->setValue(value.amount);
setCurrentWidget(ui->SendCoinsSecure);
}
} }
void SendCoinsEntry::setAddress(const QString &address) void SendCoinsEntry::setAddress(const QString &address)
@ -161,7 +178,7 @@ void SendCoinsEntry::setAddress(const QString &address)
bool SendCoinsEntry::isClear() bool SendCoinsEntry::isClear()
{ {
return ui->payTo->text().isEmpty(); return ui->payTo->text().isEmpty() && ui->payTo_s->text().isEmpty();
} }
void SendCoinsEntry::setFocus() void SendCoinsEntry::setFocus()
@ -175,5 +192,6 @@ void SendCoinsEntry::updateDisplayUnit()
{ {
// Update payAmount with the current unit // Update payAmount with the current unit
ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
ui->payAmount_s->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
} }
} }

14
src/qt/sendcoinsentry.h

@ -1,16 +1,21 @@
#ifndef SENDCOINSENTRY_H #ifndef SENDCOINSENTRY_H
#define SENDCOINSENTRY_H #define SENDCOINSENTRY_H
#include <QFrame> #include <QStackedWidget>
#include "walletmodel.h"
namespace Ui { namespace Ui {
class SendCoinsEntry; class SendCoinsEntry;
} }
class WalletModel; class WalletModel;
class SendCoinsRecipient;
/** A single entry in the dialog for sending bitcoins. */ /**
class SendCoinsEntry : public QFrame * A single entry in the dialog for sending bitcoins.
* Stacked widget, with different UIs for payment requests
* with a strong payee identity.
*/
class SendCoinsEntry : public QStackedWidget
{ {
Q_OBJECT Q_OBJECT
@ -49,6 +54,7 @@ private slots:
void updateDisplayUnit(); void updateDisplayUnit();
private: private:
SendCoinsRecipient recipient;
Ui::SendCoinsEntry *ui; Ui::SendCoinsEntry *ui;
WalletModel *model; WalletModel *model;
}; };

3
src/qt/splashscreen.cpp

@ -1,6 +1,7 @@
#include "splashscreen.h" #include "splashscreen.h"
#include "clientversion.h" #include "clientversion.h"
#include "util.h" #include "util.h"
#include "chainparams.h"
#include <QApplication> #include <QApplication>
#include <QPainter> #include <QPainter>
@ -26,7 +27,7 @@ SplashScreen::SplashScreen(const QPixmap &pixmap, Qt::WindowFlags f) :
// load the bitmap for writing some text over it // load the bitmap for writing some text over it
QPixmap newPixmap; QPixmap newPixmap;
if(GetBoolArg("-testnet", false)) { if(TestNet()) {
newPixmap = QPixmap(":/images/splash_testnet"); newPixmap = QPixmap(":/images/splash_testnet");
} }
else { else {

307
src/qt/test/paymentrequestdata.h

@ -0,0 +1,307 @@
//
// Data for paymentservertests.cpp
//
// Base64/DER-encoded fake certificate authority certificate.
// Convert pem to base64/der with:
// cat file.pem | openssl x509 -inform PEM -outform DER | openssl enc -base64
//
// Serial Number: 10302349811211485352 (0x8ef94c91b112c0a8)
// Issuer: CN=PaymentRequest Test CA
// Subject: CN=PaymentRequest Test CA
// Not Valid After : Dec 8 16:37:24 2022 GMT
//
const char* caCert_BASE64 =
"\
MIIB0DCCATmgAwIBAgIJAI75TJGxEsCoMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV\
BAMTFlBheW1lbnRSZXF1ZXN0IFRlc3QgQ0EwHhcNMTIxMjEwMTYzNzI0WhcNMjIx\
MjA4MTYzNzI0WjAhMR8wHQYDVQQDExZQYXltZW50UmVxdWVzdCBUZXN0IENBMIGf\
MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvua59nX9radoqDYyplcns5qdVDTN1\
7tmcGixmMYOYU3UYMU55VSsJs0dWKnMm3COQDY+N63c0XSbRqarBcsLTkaNASuPX\
FCv1VWuEKSyy5xe4zeoDU7CVSzlxtQD9wbZW/s3ISjgaXBpwn6eVmntb0JwYxxPc\
M1u/hrMD8BDbSQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA\
A4GBADSaRgK5xe47XxycXBhHhr0Wgl4pAsFsufqA9aB9r8KNEHJ0yUvvbD/jaJJM\
RtQcf0AJ9olzUMY4syehxbzUJP6aeXhZEYiMvdvcv9D55clq6+WLLlNT3jBgAaVn\
p3waRjPD4bUX3nv+ojz5s4puw7Qq5QUZlhGsMzPvwDGCmZkL\
";
//
// This payment request validates directly against the
// above certificate authority.
//
const char* paymentrequest1_BASE64 =
"\
Egt4NTA5K3NoYTI1NhrxAwruAzCCAeowggFToAMCAQICAQEwDQYJKoZIhvcNAQEL\
BQAwITEfMB0GA1UEAxMWUGF5bWVudFJlcXVlc3QgVGVzdCBDQTAeFw0xMjEyMTAx\
NjM3MjRaFw0yMjEyMDgxNjM3MjRaMEMxGTAXBgNVBAMMEHRlc3RtZXJjaGFudC5v\
cmcxJjAkBgNVBAoMHVBheW1lbnQgUmVxdWVzdCBUZXN0IE1lcmNoYW50MIGfMA0G\
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHkMy8W1u6HsWlSqdWTmMKf54gICxNfxbY\
+rcMtAftr62hCYx2d2QiSRd1pCUzmo12IiSX3WxSHwaTnT3MFD6jRx6+zM6XdGar\
I2zpYle11ANzu4gAthN17uRQHV2O5QxVtzNaMdKeJLXT2L9tfEdyL++9ZUqoQmdA\
YG9ix330hQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GB\
AIkyO99KC68bi9PFRyQQ7nvn5GlQEb3Ca1bRG5+AKN9N5vc8rZ9G2hejtM8wEXni\
eGBP+chVMsbTPEHKLrwREn7IvcyCcbAStaklPC3w0B/2idQSHskb6P3X13OR2bTH\
a2+6wuhsOZRUrVNr24rM95DKx/eCC6JN1VW+qRPU6fqzIjQSHwiw2wYSGXapFJVg\
igPI+6XpExtNLO/i1WFV8ZmoiKwYsuHFiwUqC1VuaXRUZXN0T25lKoABS0j59iMU\
Uc9MdIfwsO1BskIET0eJSGNZ7eXb9N62u+qf831PMpEHkmlGpk8rHy92nPcgua/U\
Yt8oZMn3QaTZ5A6HjJbc3A73eLylp1a0SwCl+KDMEvDQhqMn1jAVu2v92AH3uB7n\
SiWVbw0tX/68iSQEGGfh9n6ee/8Myb3ICdw=\
";
//
// Signed, but expired, merchant cert in the request
//
const char* paymentrequest2_BASE64 =
"\
Egt4NTA5K3NoYTI1NhrsAwrpAzCCAeUwggFOoAMCAQICAQMwDQYJKoZIhvcNAQEL\
BQAwITEfMB0GA1UEAxMWUGF5bWVudFJlcXVlc3QgVGVzdCBDQTAeFw0xMzAyMjMy\
MTI2NDNaFw0xMzAyMjQyMTI2NDNaMD4xHDAaBgNVBAMME2V4cGlyZWRtZXJjaGFu\
dC5vcmcxHjAcBgNVBAoMFUV4cGlyZWQgVGVzdCBNZXJjaGFudDCBnzANBgkqhkiG\
9w0BAQEFAAOBjQAwgYkCgYEAx5DMvFtbuh7FpUqnVk5jCn+eICAsTX8W2Pq3DLQH\
7a+toQmMdndkIkkXdaQlM5qNdiIkl91sUh8Gk509zBQ+o0cevszOl3RmqyNs6WJX\
tdQDc7uIALYTde7kUB1djuUMVbczWjHSniS109i/bXxHci/vvWVKqEJnQGBvYsd9\
9IUCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAaU137\
j53rvSjlmYZpZ4RWTP7EdD6fl5ZxBeXHytN6DQL33H0eD7OFHt+ofc7E6D7keubl\
UfCu+jOvt/MvvPUmtCI9yXZ0dNC4sjyETv+wQpxO0UNZwOM4uegdCzlo6Bi3pD4/\
KKLdMkWuUfuPBmoammny74lZaOVr5deKXztTuCI0Eh8IsNsGEhl2qRSVYIoDyPul\
6RMbTSzv4tVhVfGZqIisGLLhxYsFKgtVbml0VGVzdFR3byqAAXHuo4nZEPniLpkd\
y30TkwBxVgprWJ18a9z/7Py35Qss/JMbOXbnBhJtmJCdIowHRI0aa+zqt3KKKAXi\
mm+V4seMgxTcxMS+eDDkiTcB/RtWWSyRcS2ANjFeY0T4SLMwiCL9qWPi03hr8j96\
tejrSPOBNSJ3Mi/q5u2Yl4gJZY2b\
";
//
// 10-long chain, all intermediates valid
//
const char* paymentrequest3_BASE64 =
"\
Egt4NTA5K3NoYTI1Nhq8JAr/AzCCAfswggFkoAMCAQICAQEwDQYJKoZIhvcNAQEL\
BQAwPzEUMBIGA1UEAwwLdGVzdGNhOC5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVx\
dWVzdCBJbnRlcm1lZGlhdGUgODAeFw0xMzAyMjMyMjQyMzFaFw0yMzAyMjEyMjQy\
MzFaMDYxGjAYBgNVBAMMEXRlc3RtZXJjaGFudDgub3JnMRgwFgYDVQQKDA9UZXN0\
IE1lcmNoYW50IDgwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMMCHA3hiHbS\
TKZ5K9jHRwE8NxkGp3IOx56PDB2diNkldG8XweTcRq7bBm7pdiBt4IVggtfs+6hE\
hDYIOecyoAnVzPFTdvQ7KQdQ/fD9YLe6lk+o0edOqutPMyrxLFjSluXxEQyk7fdt\
URloMMYfp3p1/hFCboA1rAsQ2RW38hR5AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w\
DQYJKoZIhvcNAQELBQADgYEAPsdFatnc2RJSpvZsw+nCiPVsllycw5ELglq9vfJz\
nJJucRxgzmqI2iuas1ugwbXn0BEIRLK7vMF/qBzQR6M/nTxttah+KEu+okjps9vJ\
cIyhfTyGPC5xkHaHZ7sG+UHOFhPw0/kXn0x+pbVgBZ5315axqcp1R+DTSj/whMAr\
n0AKiAQwggIEMIIBbaADAgECAgECMA0GCSqGSIb3DQEBCwUAMD8xFDASBgNVBAMM\
C3Rlc3RjYTcub3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRp\
YXRlIDcwHhcNMTMwMjIzMjI0MjMxWhcNMjMwMjIxMjI0MjMxWjA/MRQwEgYDVQQD\
DAt0ZXN0Y2E4Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVk\
aWF0ZSA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDexUFfxb1sThvabp7u\
dZz59ciThGmmAW0nP4tjrgEACgvWIInr2dZpTHbiQNF34ycsk0le1JD93D7Qb8rd\
25OrpaO8XS2Li2zjR9cleixXjSLwV/zv8zJ8yPl/27XL++PDTKBXVpJ8/Syp+9Ty\
plV1BqDhqtIHb/QSHEkTQXjeYQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqG\
SIb3DQEBCwUAA4GBACMooQVbkbIZ2DaPwHDc4ULwguG3VI2Kzj50UdExmHtzm2S4\
MQei+n+HEPjtJAx5OY520+10nfuP+12H2DRLQmWmdvDpeQ/Cv0yavlw4ZRejRFo7\
KS83C0wo5rd+qTvvOmAN4UTArWkzYcEUulPdiXnRamb0WQHTeVdIbHVkMormCogE\
MIICBDCCAW2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MRQwEgYDVQQDDAt0ZXN0\
Y2E2Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSA2\
MB4XDTEzMDIyMzIyNDIzMVoXDTIzMDIyMTIyNDIzMVowPzEUMBIGA1UEAwwLdGVz\
dGNhNy5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUg\
NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtjBRazrkebXAhXsbjimrMIRm\
W/f9SwAHwXfc042keNtl0t2z6XE6UPcR2v/KrssXuCZgodeYxz6IM6lWosCM1xot\
C3ChKKFBfVO30reuKBRUxXfKAFqxaG0YOAEzdZkkY9AGhqWloeSmgxpIfhInU0EF\
JjCwrJ6IkijBatGoAAECAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B\
AQsFAAOBgQDBRTi1MolmOA0niHYX0A2lN5QWHkCfX0A7GwyoMA3dvM45m/NYd4WB\
X+HwfnfYcI6X9jOgNo5OWmc4GGsld0HlxwMYEKISBS9PbSHPBrb3TBOlw5ztQpXZ\
91+bOhLux52Fr03sK7v9qExmBM12M8UR2ltpzAMiUgLLMHyPfiWkvQqIBDCCAgQw\
ggFtoAMCAQICAQIwDQYJKoZIhvcNAQELBQAwPzEUMBIGA1UEAwwLdGVzdGNhNS5v\
cmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgNTAeFw0x\
MzAyMjMyMjQyMzBaFw0yMzAyMjEyMjQyMzBaMD8xFDASBgNVBAMMC3Rlc3RjYTYu\
b3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDYwgZ8w\
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANJSH3xivX1t9olIdHsznI1aE9SD7t9i\
SZJsIB0otoETHZRVv9M9LvyzBNK98ZV+kTOlST7PJgC0d9BQM9sgYApSRq5oqKDM\
9FXbOm/yaReAbU3mkFNFw5roTlJ5ThEy0yOGT/DS0YBRaGIvRPRj2DiqDVdCZZ+w\
4jo1IYHkZt4FAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQAD\
gYEATm6+J1OmbrothO60xALKonWMBKr6hudb4amkFBqKbA9wMeM3jl+I/yKfz/Uf\
xWuJ071IhiNv6Gxx5YwNvhUe1xMhUqHv0gpyK1Z47bD+kYS2se5sWNPNo3Y9qZDG\
IXiGQxwHmrzaFk79Uy1xsmvsEz42w6hr25Yaw7HkIgrFveoKiAQwggIEMIIBbaAD\
AgECAgECMA0GCSqGSIb3DQEBCwUAMD8xFDASBgNVBAMMC3Rlc3RjYTQub3JnMScw\
JQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDQwHhcNMTMwMjIz\
MjI0MjMwWhcNMjMwMjIxMjI0MjMwWjA/MRQwEgYDVQQDDAt0ZXN0Y2E1Lm9yZzEn\
MCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSA1MIGfMA0GCSqG\
SIb3DQEBAQUAA4GNADCBiQKBgQC7vVUFpxHzz2Tr/xij3k58s8d/BPA0R6D5RXTV\
vmhAzc1Zuin4zUKRFs/aCj/0yED8Wu/COfNGF4tVlRNMdl9EcFsxa8XGEL4eAZa+\
H/rOHH+7/1EINrrVWhZlUecyhilN8jmCZmqEM3ecuD0NAViqyMrgmaiFmsLoQZpE\
GepDUQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAEdJ\
Ss8jWiooja3WZzHXeF95QkBJNjIlpDLGcpl4opOYLSuEl9Uxp//LaQQiXuzpj4/I\
pkWGQmMy5HOyH1lqDyiMgXpcG8PE0jEQAoEUGZ0QEqB1mZ6BCrYvmUuf/5aSVd8Y\
6lKMR3WzFDYU9Zy0nzuHB/3nvp6MeDRQeRMtYvz4CogEMIICBDCCAW2gAwIBAgIB\
AjANBgkqhkiG9w0BAQsFADA/MRQwEgYDVQQDDAt0ZXN0Y2EzLm9yZzEnMCUGA1UE\
CgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSAzMB4XDTEzMDIyMzIyNDIy\
OVoXDTIzMDIyMTIyNDIyOVowPzEUMBIGA1UEAwwLdGVzdGNhNC5vcmcxJzAlBgNV\
BAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgNDCBnzANBgkqhkiG9w0B\
AQEFAAOBjQAwgYkCgYEAxYYo3w2UXiYg6O8b4QgwN/vgreTkiW122Ep/z2TiDrhV\
MhfOOiKdwYESPflfnXnVaQQzCGexYTQqsvqvzHSyna5hL0zPTRJxSKmTVrXRsWtp\
dCRhjxCGipS3tlQBDi7vb+7SNRIBK4dBjjGzALNk7gMCpy+yM8f6I043jTlmGb0C\
AwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQDU+IQxt3Oh\
KqaUYWC23+cB2gekvWqwMBnrCNrX/Dp+kjoJKUoR2Fs3qw53raHES4SIhpGT9l9l\
rppNQgFe/JMHeYqOZMZO+6kuU0olJanBJ14tPIc7zlMTQ9OfmZ6v07IpyFbsQDtR\
hpe80DpuvSFPfJ4fh0WrQf6kn3KDVpGDnAqIBDCCAgQwggFtoAMCAQICAQIwDQYJ\
KoZIhvcNAQELBQAwPzEUMBIGA1UEAwwLdGVzdGNhMi5vcmcxJzAlBgNVBAoMHlBh\
eW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgMjAeFw0xMzAyMjMyMjQyMjlaFw0y\
MzAyMjEyMjQyMjlaMD8xFDASBgNVBAMMC3Rlc3RjYTMub3JnMScwJQYDVQQKDB5Q\
YXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDMwgZ8wDQYJKoZIhvcNAQEBBQAD\
gY0AMIGJAoGBANzgVP99Qg98e6NsKEz1v5KqRB7NTBRRsYnBvb/TSWipvMQaCYuE\
yk1xG57x++QuASKeR3QHRQJOoAhQaj9JLUhSSv9GQ5PrFLLsOFv7L1tpzXHh2dOB\
IW92X2yFRW2s39q+Q21yvN+N8uoKdqXhzRA+dDoXh3cavaVeHX1G+IrlAgMBAAGj\
EDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEASTwg84cX+1UhOG9s\
ejFV3m34QuI1hPZ+qhqVJlRYUtego8Wng1BburDSwqVAv4ch2wi3c2s4e8J7AXyL\
tzSbSQG4RN0oZi0mR8EtTTN+Mix/hBIk79dMZg85+I29uFA6Zj2d9oAhQv2qkHhc\
6tcaheNvkQRlCyH68k3iF1Fqf+4KiAQwggIEMIIBbaADAgECAgECMA0GCSqGSIb3\
DQEBCwUAMD8xFDASBgNVBAMMC3Rlc3RjYTEub3JnMScwJQYDVQQKDB5QYXltZW50\
IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDEwHhcNMTMwMjIzMjI0MjI5WhcNMjMwMjIx\
MjI0MjI5WjA/MRQwEgYDVQQDDAt0ZXN0Y2EyLm9yZzEnMCUGA1UECgweUGF5bWVu\
dCBSZXF1ZXN0IEludGVybWVkaWF0ZSAyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB\
iQKBgQDaV8zhfyQuSf/f+fauMfgs3g/RnWy9yxxUkvQneQQPH3uZzCyk3A6q72ip\
TtwNqiibG9455L9A7SaUjGtnpUz0NKT/VWUdqbfCl1PqXjEZbDobbAQ5hxLGOTyL\
RQhLIcgeq2/BnmeCqHsC4md04nUp+nBo1HwKyygvK+9sMbCp/wIDAQABoxAwDjAM\
BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBACvYyE+PPmWFkbjyRu9LAt8D\
crtyYYLRClKSg6tVvutwukLG2l//kDOohYkJtgTqr6LnCIIIwYdXN+4wxugmw4cn\
PIZmP6kovxjhhVM95okilor1zniTAo3RN7JDIfTGNgxLdGu1btt7DOFL4zTbeSJM\
b8M1JpPftehH+x/VLyuUCuoDMIIB5jCCAU+gAwIBAgIBBTANBgkqhkiG9w0BAQsF\
ADAhMR8wHQYDVQQDExZQYXltZW50UmVxdWVzdCBUZXN0IENBMB4XDTEzMDIyMzIy\
NDIyOFoXDTIzMDIyMTIyNDIyOFowPzEUMBIGA1UEAwwLdGVzdGNhMS5vcmcxJzAl\
BgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgMTCBnzANBgkqhkiG\
9w0BAQEFAAOBjQAwgYkCgYEAo5Vy9H3nA/OOkF5Ap89yfVNSiTay/LYCaB0eALpc\
U690U75O9Q3w2M+2AN8wpbbHsJHZMIjEeBRoQfjlYXW1ucQTxWKyT+liu0D25mGX\
X27CBXBd4iXTxVII/iX+u3lcjORjoHOBy7QgeIDIIS9y0vYu8eArpjh7m4thrVgI\
RtMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQB9LKcV\
JK9sjASNzpQlpUp7nCiw5FSjVY+XMRIKK/kavzlKjZ+InsmmyRVGjDoZi9GrqG9P\
VHgLBxi2VtVjmokZoNPqao3OfhqORAubC+JR/JLepM7aDaxDdTHVhSUk4lgNAvi2\
6dGY7nZMsnHlPQ2tPp/HvRRiMq1oDjlylc8VTCI2Eh8IsNsGEhl2qRSVYIoDyPul\
6RMbTSzv4tVhVfGZqIisGLLhxYsFKg1Vbml0VGVzdFRocmVlKoABn2HTsUQtMNI4\
yNvkfkFNka3pRvTUTydJrvyfmEeLzImfM1BWddZjnywku9RToNFZZNgow5QnljmF\
chhR/aHOuEMTxmc12K4rNlgYtHCsxLP9zd+6u0cva3TucZ6EzS8PKEib/+r12/52\
664NuWA9WtsK7QCFrK2K95PnVCRmWl0=\
";
//
// Long chain, with an invalid (expired) cert in the middle
//
const char* paymentrequest4_BASE64 =
"\
Egt4NTA5K3NoYTI1NhqeJAr/AzCCAfswggFkoAMCAQICAQEwDQYJKoZIhvcNAQEL\
BQAwPzEUMBIGA1UEAwwLdGVzdGNhOC5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVx\
dWVzdCBJbnRlcm1lZGlhdGUgODAeFw0xMzAyMjMyMjQyMzFaFw0yMzAyMjEyMjQy\
MzFaMDYxGjAYBgNVBAMMEXRlc3RtZXJjaGFudDgub3JnMRgwFgYDVQQKDA9UZXN0\
IE1lcmNoYW50IDgwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMMCHA3hiHbS\
TKZ5K9jHRwE8NxkGp3IOx56PDB2diNkldG8XweTcRq7bBm7pdiBt4IVggtfs+6hE\
hDYIOecyoAnVzPFTdvQ7KQdQ/fD9YLe6lk+o0edOqutPMyrxLFjSluXxEQyk7fdt\
URloMMYfp3p1/hFCboA1rAsQ2RW38hR5AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w\
DQYJKoZIhvcNAQELBQADgYEAPsdFatnc2RJSpvZsw+nCiPVsllycw5ELglq9vfJz\
nJJucRxgzmqI2iuas1ugwbXn0BEIRLK7vMF/qBzQR6M/nTxttah+KEu+okjps9vJ\
cIyhfTyGPC5xkHaHZ7sG+UHOFhPw0/kXn0x+pbVgBZ5315axqcp1R+DTSj/whMAr\
n0AKiAQwggIEMIIBbaADAgECAgECMA0GCSqGSIb3DQEBCwUAMD8xFDASBgNVBAMM\
C3Rlc3RjYTcub3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRp\
YXRlIDcwHhcNMTMwMjIzMjI0MjMxWhcNMjMwMjIxMjI0MjMxWjA/MRQwEgYDVQQD\
DAt0ZXN0Y2E4Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVk\
aWF0ZSA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDexUFfxb1sThvabp7u\
dZz59ciThGmmAW0nP4tjrgEACgvWIInr2dZpTHbiQNF34ycsk0le1JD93D7Qb8rd\
25OrpaO8XS2Li2zjR9cleixXjSLwV/zv8zJ8yPl/27XL++PDTKBXVpJ8/Syp+9Ty\
plV1BqDhqtIHb/QSHEkTQXjeYQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqG\
SIb3DQEBCwUAA4GBACMooQVbkbIZ2DaPwHDc4ULwguG3VI2Kzj50UdExmHtzm2S4\
MQei+n+HEPjtJAx5OY520+10nfuP+12H2DRLQmWmdvDpeQ/Cv0yavlw4ZRejRFo7\
KS83C0wo5rd+qTvvOmAN4UTArWkzYcEUulPdiXnRamb0WQHTeVdIbHVkMormCogE\
MIICBDCCAW2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MRQwEgYDVQQDDAt0ZXN0\
Y2E2Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSA2\
MB4XDTEzMDIyMzIyNDIzMVoXDTIzMDIyMTIyNDIzMVowPzEUMBIGA1UEAwwLdGVz\
dGNhNy5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUg\
NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtjBRazrkebXAhXsbjimrMIRm\
W/f9SwAHwXfc042keNtl0t2z6XE6UPcR2v/KrssXuCZgodeYxz6IM6lWosCM1xot\
C3ChKKFBfVO30reuKBRUxXfKAFqxaG0YOAEzdZkkY9AGhqWloeSmgxpIfhInU0EF\
JjCwrJ6IkijBatGoAAECAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B\
AQsFAAOBgQDBRTi1MolmOA0niHYX0A2lN5QWHkCfX0A7GwyoMA3dvM45m/NYd4WB\
X+HwfnfYcI6X9jOgNo5OWmc4GGsld0HlxwMYEKISBS9PbSHPBrb3TBOlw5ztQpXZ\
91+bOhLux52Fr03sK7v9qExmBM12M8UR2ltpzAMiUgLLMHyPfiWkvQqIBDCCAgQw\
ggFtoAMCAQICAQIwDQYJKoZIhvcNAQELBQAwPzEUMBIGA1UEAwwLdGVzdGNhNS5v\
cmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgNTAeFw0x\
MzAyMjMyMjQyMzBaFw0yMzAyMjEyMjQyMzBaMD8xFDASBgNVBAMMC3Rlc3RjYTYu\
b3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDYwgZ8w\
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANJSH3xivX1t9olIdHsznI1aE9SD7t9i\
SZJsIB0otoETHZRVv9M9LvyzBNK98ZV+kTOlST7PJgC0d9BQM9sgYApSRq5oqKDM\
9FXbOm/yaReAbU3mkFNFw5roTlJ5ThEy0yOGT/DS0YBRaGIvRPRj2DiqDVdCZZ+w\
4jo1IYHkZt4FAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQAD\
gYEATm6+J1OmbrothO60xALKonWMBKr6hudb4amkFBqKbA9wMeM3jl+I/yKfz/Uf\
xWuJ071IhiNv6Gxx5YwNvhUe1xMhUqHv0gpyK1Z47bD+kYS2se5sWNPNo3Y9qZDG\
IXiGQxwHmrzaFk79Uy1xsmvsEz42w6hr25Yaw7HkIgrFveoK6gMwggHmMIIBT6AD\
AgECAgEGMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFlBheW1lbnRSZXF1ZXN0\
IFRlc3QgQ0EwHhcNMTMwMjIzMjI1OTUxWhcNMTMwMjI0MjI1OTUxWjA/MRQwEgYD\
VQQDDAt0ZXN0Y2E1Lm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVy\
bWVkaWF0ZSA1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7vVUFpxHzz2Tr\
/xij3k58s8d/BPA0R6D5RXTVvmhAzc1Zuin4zUKRFs/aCj/0yED8Wu/COfNGF4tV\
lRNMdl9EcFsxa8XGEL4eAZa+H/rOHH+7/1EINrrVWhZlUecyhilN8jmCZmqEM3ec\
uD0NAViqyMrgmaiFmsLoQZpEGepDUQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0G\
CSqGSIb3DQEBCwUAA4GBAEmcUEnhua/oiXy1fwScLgMqt+jk9mHRpE6SVsIop23Q\
CY2JfpG6RxhMMzzzhGklEGN6cxG0HCi6B3HJx6PYrFEfTB0rW4K6m0Tvx3WpS9mN\
uoEuJHLy18ausI/sYAPDHCL+SfBVcqorpaIG2sSpZouRBjRHAyqFAYlwlW87uq5n\
CogEMIICBDCCAW2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MRQwEgYDVQQDDAt0\
ZXN0Y2EzLm9yZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0\
ZSAzMB4XDTEzMDIyMzIyNDIyOVoXDTIzMDIyMTIyNDIyOVowPzEUMBIGA1UEAwwL\
dGVzdGNhNC5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlh\
dGUgNDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxYYo3w2UXiYg6O8b4Qgw\
N/vgreTkiW122Ep/z2TiDrhVMhfOOiKdwYESPflfnXnVaQQzCGexYTQqsvqvzHSy\
na5hL0zPTRJxSKmTVrXRsWtpdCRhjxCGipS3tlQBDi7vb+7SNRIBK4dBjjGzALNk\
7gMCpy+yM8f6I043jTlmGb0CAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG\
9w0BAQsFAAOBgQDU+IQxt3OhKqaUYWC23+cB2gekvWqwMBnrCNrX/Dp+kjoJKUoR\
2Fs3qw53raHES4SIhpGT9l9lrppNQgFe/JMHeYqOZMZO+6kuU0olJanBJ14tPIc7\
zlMTQ9OfmZ6v07IpyFbsQDtRhpe80DpuvSFPfJ4fh0WrQf6kn3KDVpGDnAqIBDCC\
AgQwggFtoAMCAQICAQIwDQYJKoZIhvcNAQELBQAwPzEUMBIGA1UEAwwLdGVzdGNh\
Mi5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1lZGlhdGUgMjAe\
Fw0xMzAyMjMyMjQyMjlaFw0yMzAyMjEyMjQyMjlaMD8xFDASBgNVBAMMC3Rlc3Rj\
YTMub3JnMScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDMw\
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANzgVP99Qg98e6NsKEz1v5KqRB7N\
TBRRsYnBvb/TSWipvMQaCYuEyk1xG57x++QuASKeR3QHRQJOoAhQaj9JLUhSSv9G\
Q5PrFLLsOFv7L1tpzXHh2dOBIW92X2yFRW2s39q+Q21yvN+N8uoKdqXhzRA+dDoX\
h3cavaVeHX1G+IrlAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL\
BQADgYEASTwg84cX+1UhOG9sejFV3m34QuI1hPZ+qhqVJlRYUtego8Wng1BburDS\
wqVAv4ch2wi3c2s4e8J7AXyLtzSbSQG4RN0oZi0mR8EtTTN+Mix/hBIk79dMZg85\
+I29uFA6Zj2d9oAhQv2qkHhc6tcaheNvkQRlCyH68k3iF1Fqf+4KiAQwggIEMIIB\
baADAgECAgECMA0GCSqGSIb3DQEBCwUAMD8xFDASBgNVBAMMC3Rlc3RjYTEub3Jn\
MScwJQYDVQQKDB5QYXltZW50IFJlcXVlc3QgSW50ZXJtZWRpYXRlIDEwHhcNMTMw\
MjIzMjI0MjI5WhcNMjMwMjIxMjI0MjI5WjA/MRQwEgYDVQQDDAt0ZXN0Y2EyLm9y\
ZzEnMCUGA1UECgweUGF5bWVudCBSZXF1ZXN0IEludGVybWVkaWF0ZSAyMIGfMA0G\
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaV8zhfyQuSf/f+fauMfgs3g/RnWy9yxxU\
kvQneQQPH3uZzCyk3A6q72ipTtwNqiibG9455L9A7SaUjGtnpUz0NKT/VWUdqbfC\
l1PqXjEZbDobbAQ5hxLGOTyLRQhLIcgeq2/BnmeCqHsC4md04nUp+nBo1HwKyygv\
K+9sMbCp/wIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GB\
ACvYyE+PPmWFkbjyRu9LAt8DcrtyYYLRClKSg6tVvutwukLG2l//kDOohYkJtgTq\
r6LnCIIIwYdXN+4wxugmw4cnPIZmP6kovxjhhVM95okilor1zniTAo3RN7JDIfTG\
NgxLdGu1btt7DOFL4zTbeSJMb8M1JpPftehH+x/VLyuUCuoDMIIB5jCCAU+gAwIB\
AgIBBTANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDExZQYXltZW50UmVxdWVzdCBU\
ZXN0IENBMB4XDTEzMDIyMzIyNDIyOFoXDTIzMDIyMTIyNDIyOFowPzEUMBIGA1UE\
AwwLdGVzdGNhMS5vcmcxJzAlBgNVBAoMHlBheW1lbnQgUmVxdWVzdCBJbnRlcm1l\
ZGlhdGUgMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAo5Vy9H3nA/OOkF5A\
p89yfVNSiTay/LYCaB0eALpcU690U75O9Q3w2M+2AN8wpbbHsJHZMIjEeBRoQfjl\
YXW1ucQTxWKyT+liu0D25mGXX27CBXBd4iXTxVII/iX+u3lcjORjoHOBy7QgeIDI\
IS9y0vYu8eArpjh7m4thrVgIRtMCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkq\
hkiG9w0BAQsFAAOBgQB9LKcVJK9sjASNzpQlpUp7nCiw5FSjVY+XMRIKK/kavzlK\
jZ+InsmmyRVGjDoZi9GrqG9PVHgLBxi2VtVjmokZoNPqao3OfhqORAubC+JR/JLe\
pM7aDaxDdTHVhSUk4lgNAvi26dGY7nZMsnHlPQ2tPp/HvRRiMq1oDjlylc8VTCI1\
Eh8IsNsGEhl2qRSVYIoDyPul6RMbTSzv4tVhVfGZqIisGLLhxYsFKgxVbml0VGVz\
dEZvdXIqgAEBE1PP93Tkpif35F+dYmXn9kLA/1djcPjCs2o2rwRMM4Uk356O5dgu\
HXQjsfdR58qZQS9CS5DAtRUf0R8+43/wijO/hb49VNaNXmY+/cPHMkahP2aV3tZi\
FAyZblLik9A7ZvF+UsjeFQiHB5wzWQvbqk5wQ4yabHIXoYv/E0q+eQ==\
";
const char* paymentrequest5_BASE64 =
"\
Egt4NTA5K3NoYTI1NhrxAwruAzCCAeowggFToAMCAQICAQEwDQYJKoZIhvcNAQEL\
BQAwITEfMB0GA1UEAxMWUGF5bWVudFJlcXVlc3QgVGVzdCBDQTAeFw0xMzA0MTkx\
NzIwMDZaFw0yMzA0MTcxNzIwMDZaMEMxGTAXBgNVBAMMEHRlc3RtZXJjaGFudC5v\
cmcxJjAkBgNVBAoMHVBheW1lbnQgUmVxdWVzdCBUZXN0IE1lcmNoYW50MIGfMA0G\
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhV6Yn47aEEmbl50YLvXoqGEJA51I/40wr\
Z6VQGdXYaRqYktagrWDlgYY9h0JQ1bQhm8HgW7ju0R4NaDTXUqxg4HjprF0z3Mfm\
/6mmebkLOOptfkVD7ceAteNI7cyuqWGIAZA7D9mV97mXoCAtTlBUycvkmoiClCCS\
h0EpF/UTaQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GB\
AGIRwW7I0QvLga+RnJoJSZNZQbtu4rQW3xmoz8WfZMBYXX3QBYg5ftycbdK+/IbP\
qozfjGW2AS6DNArvpveSPDTK9+GJBNo1paiNtVqwXkC3Ddscv5AIms1eZGiIOQNC\
mUvdLkpoXo48WAer3EGsZ3B15GyNEELc0q9W5yUebba1IjUSHwiw2wYSGXapFJVg\
igPI+6XpExtNLO/i1WFV8ZmoiKwYuPvFiwUqDFVuaXRUZXN0Rml2ZSqAAXdsMgdG\
ssymvca1S/1KeM3n8Ydi2fi1JUzAAr59xPvNJRUeqCLP9upHn5z7br3P12Oz9A20\
5/4wL4ClPRPVnOHgij0bEg+y0tGESqmF1rfOfXDszlo2U92wCxS07kq79YAZJ1Zo\
XYh860/Q4wvc7lfiTe+dXBzPKAKhMy91yETY\
";

109
src/qt/test/paymentservertests.cpp

@ -0,0 +1,109 @@
#include <QCoreApplication>
#include <QDebug>
#include <QTemporaryFile>
#include <QVariant>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>
#include "optionsmodel.h"
#include "paymentservertests.h"
#include "paymentrequestdata.h"
#include "util.h"
X509 *parse_b64der_cert(const char* cert_data)
{
std::vector<unsigned char> data = DecodeBase64(cert_data);
assert(data.size() > 0);
const unsigned char* dptr = &data[0];
X509 *cert = d2i_X509(NULL, &dptr, data.size());
assert(cert);
return cert;
}
//
// Test payment request handling
//
static SendCoinsRecipient handleRequest(PaymentServer* server, std::vector<unsigned char>& data)
{
RecipientCatcher sigCatcher;
QObject::connect(server, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)),
&sigCatcher, SLOT(getRecipient(SendCoinsRecipient)));
// Write data to a temp file:
QTemporaryFile f;
f.open();
f.write((const char*)&data[0], data.size());
f.close();
// Create a FileOpenEvent and send it directly to the server's event filter:
QFileOpenEvent event(f.fileName());
server->eventFilter(NULL, &event);
QObject::disconnect(server, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)),
&sigCatcher, SLOT(getRecipient(SendCoinsRecipient)));
// Return results from sigCatcher
return sigCatcher.recipient;
}
void PaymentServerTests::paymentServerTests()
{
OptionsModel optionsModel;
PaymentServer* server = new PaymentServer(NULL, false);
X509_STORE* caStore = X509_STORE_new();
X509_STORE_add_cert(caStore, parse_b64der_cert(caCert_BASE64));
PaymentServer::LoadRootCAs(caStore);
server->initNetManager(optionsModel);
server->uiReady();
// Now feed PaymentRequests to server, and observe signals it produces:
std::vector<unsigned char> data = DecodeBase64(paymentrequest1_BASE64);
SendCoinsRecipient r = handleRequest(server, data);
QString merchant;
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString("testmerchant.org"));
// Version of the above, with an expired certificate:
data = DecodeBase64(paymentrequest2_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString(""));
// Long certificate chain:
data = DecodeBase64(paymentrequest3_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString("testmerchant8.org"));
// Long certificate chain, with an expired certificate in the middle:
data = DecodeBase64(paymentrequest4_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString(""));
// Validly signed, but by a CA not in our root CA list:
data = DecodeBase64(paymentrequest5_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString(""));
// Try again with no root CA's, verifiedMerchant should be empty:
caStore = X509_STORE_new();
PaymentServer::LoadRootCAs(caStore);
data = DecodeBase64(paymentrequest1_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString(""));
delete server;
}
void RecipientCatcher::getRecipient(SendCoinsRecipient r)
{
recipient = r;
}

29
src/qt/test/paymentservertests.h

@ -0,0 +1,29 @@
#ifndef PAYMENTSERVERTESTS_H
#define PAYMENTSERVERTESTS_H
#include <QTest>
#include <QObject>
#include "../paymentserver.h"
class PaymentServerTests : public QObject
{
Q_OBJECT
private slots:
void paymentServerTests();
};
// Dummy class to receive paymentserver signals.
// If SendCoinsRecipient was a proper QObject, then we could use
// QSignalSpy... but it's not.
class RecipientCatcher : public QObject
{
Q_OBJECT
public slots:
void getRecipient(SendCoinsRecipient r);
public:
SendCoinsRecipient recipient;
};
#endif // PAYMENTSERVERTESTS_H

37
src/qt/test/test_main.cpp

@ -1,16 +1,21 @@
#include <QTest> #include <QTest>
#include <QObject> #include <QObject>
#include "uritests.h" #include "uritests.h"
#include "paymentservertests.h"
// This is all you need to run all the tests
int main(int argc, char *argv[]) // This is all you need to run all the tests
{ int main(int argc, char *argv[])
bool fInvalid = false; {
bool fInvalid = false;
URITests test1;
if (QTest::qExec(&test1) != 0) URITests test1;
fInvalid = true; if (QTest::qExec(&test1) != 0)
fInvalid = true;
return fInvalid;
} PaymentServerTests test2;
if (QTest::qExec(&test2) != 0)
fInvalid = true;
return fInvalid;
}

32
src/qt/transactiondesc.cpp

@ -7,6 +7,7 @@
#include "db.h" #include "db.h"
#include "ui_interface.h" #include "ui_interface.h"
#include "base58.h" #include "base58.h"
#include "paymentserver.h"
#include <string> #include <string>
@ -88,8 +89,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>"; strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
strHTML += "<b>" + tr("To") + ":</b> "; strHTML += "<b>" + tr("To") + ":</b> ";
strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString()); strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
if (!wallet->mapAddressBook[address].empty()) if (!wallet->mapAddressBook[address].name.empty())
strHTML += " (" + tr("own address") + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + ")"; strHTML += " (" + tr("own address") + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + ")";
else else
strHTML += " (" + tr("own address") + ")"; strHTML += " (" + tr("own address") + ")";
strHTML += "<br>"; strHTML += "<br>";
@ -110,8 +111,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
std::string strAddress = wtx.mapValue["to"]; std::string strAddress = wtx.mapValue["to"];
strHTML += "<b>" + tr("To") + ":</b> "; strHTML += "<b>" + tr("To") + ":</b> ";
CTxDestination dest = CBitcoinAddress(strAddress).Get(); CTxDestination dest = CBitcoinAddress(strAddress).Get();
if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].empty()) if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].name.empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest]) + " "; strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest].name) + " ";
strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>"; strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
} }
@ -167,8 +168,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
if (ExtractDestination(txout.scriptPubKey, address)) if (ExtractDestination(txout.scriptPubKey, address))
{ {
strHTML += "<b>" + tr("To") + ":</b> "; strHTML += "<b>" + tr("To") + ":</b> ";
if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty()) if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + " "; strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " ";
strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString()); strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
strHTML += "<br>"; strHTML += "<br>";
} }
@ -216,6 +217,21 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
strHTML += "<b>" + tr("Transaction ID") + ":</b> " + wtx.GetHash().ToString().c_str() + "<br>"; strHTML += "<b>" + tr("Transaction ID") + ":</b> " + wtx.GetHash().ToString().c_str() + "<br>";
//
// PaymentRequest info:
//
foreach (const PAIRTYPE(string, string)& r, wtx.vOrderForm)
{
if (r.first == "PaymentRequest")
{
PaymentRequestPlus req;
req.parse(QByteArray::fromRawData(r.second.c_str(), r.second.size()));
QString merchant;
if (req.getMerchant(PaymentServer::getCertStore(), merchant))
strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
}
}
if (wtx.IsCoinBase()) if (wtx.IsCoinBase())
strHTML += "<br>" + tr("Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.") + "<br>"; strHTML += "<br>" + tr("Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.") + "<br>";
@ -254,8 +270,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
CTxDestination address; CTxDestination address;
if (ExtractDestination(vout.scriptPubKey, address)) if (ExtractDestination(vout.scriptPubKey, address))
{ {
if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty()) if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty())
strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + " "; strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " ";
strHTML += QString::fromStdString(CBitcoinAddress(address).ToString()); strHTML += QString::fromStdString(CBitcoinAddress(address).ToString());
} }
strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, vout.nValue); strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, vout.nValue);

4
src/qt/walletframe.cpp

@ -52,9 +52,9 @@ void WalletFrame::removeAllWallets()
walletStack->removeAllWallets(); walletStack->removeAllWallets();
} }
bool WalletFrame::handleURI(const QString &uri) bool WalletFrame::handlePaymentRequest(const SendCoinsRecipient &recipient)
{ {
return walletStack->handleURI(uri); return walletStack->handlePaymentRequest(recipient);
} }
void WalletFrame::showOutOfSyncWarning(bool fShow) void WalletFrame::showOutOfSyncWarning(bool fShow)

5
src/qt/walletframe.h

@ -11,6 +11,7 @@
class BitcoinGUI; class BitcoinGUI;
class ClientModel; class ClientModel;
class SendCoinsRecipient;
class WalletModel; class WalletModel;
class WalletStack; class WalletStack;
@ -29,7 +30,7 @@ public:
void removeAllWallets(); void removeAllWallets();
bool handleURI(const QString &uri); bool handlePaymentRequest(const SendCoinsRecipient& recipient);
void showOutOfSyncWarning(bool fShow); void showOutOfSyncWarning(bool fShow);
@ -71,4 +72,4 @@ public slots:
void setEncryptionStatus(); void setEncryptionStatus();
}; };
#endif // WALLETFRAME_H #endif // WALLETFRAME_H

98
src/qt/walletmodel.cpp

@ -127,31 +127,60 @@ bool WalletModel::validateAddress(const QString &address)
WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipient> &recipients) WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipient> &recipients)
{ {
qint64 total = 0; qint64 total = 0;
QSet<QString> setAddress; std::vector<std::pair<CScript, int64> > vecSend;
QString hex; QByteArray transaction;
if(recipients.empty()) if(recipients.empty())
{ {
return OK; return OK;
} }
QSet<QString> setAddress; // Used to detect duplicates
int nAddresses = 0;
// Pre-check input data for validity // Pre-check input data for validity
foreach(const SendCoinsRecipient &rcp, recipients) foreach(const SendCoinsRecipient &rcp, recipients)
{ {
if(!validateAddress(rcp.address)) if (rcp.paymentRequest.IsInitialized())
{ { // PaymentRequest...
return InvalidAddress; int64 subtotal = 0;
const payments::PaymentDetails& details = rcp.paymentRequest.getDetails();
for (int i = 0; i < details.outputs_size(); i++)
{
const payments::Output& out = details.outputs(i);
if (out.amount() <= 0) continue;
subtotal += out.amount();
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
CScript scriptPubKey(scriptStr, scriptStr+out.script().size());
vecSend.push_back(std::pair<CScript, int64>(scriptPubKey, out.amount()));
}
if (subtotal <= 0)
{
return InvalidAmount;
}
total += subtotal;
} }
setAddress.insert(rcp.address); else
{ // User-entered bitcoin address / amount:
if(!validateAddress(rcp.address))
{
return InvalidAddress;
}
if(rcp.amount <= 0)
{
return InvalidAmount;
}
setAddress.insert(rcp.address);
++nAddresses;
if(rcp.amount <= 0) CScript scriptPubKey;
{ scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
return InvalidAmount; vecSend.push_back(std::pair<CScript, int64>(scriptPubKey, rcp.amount));
total += rcp.amount;
} }
total += rcp.amount;
} }
if(setAddress.size() != nAddresses)
if(recipients.size() > setAddress.size())
{ {
return DuplicateAddress; return DuplicateAddress;
} }
@ -169,19 +198,10 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
{ {
LOCK2(cs_main, wallet->cs_wallet); LOCK2(cs_main, wallet->cs_wallet);
// Sendmany
std::vector<std::pair<CScript, int64> > vecSend;
foreach(const SendCoinsRecipient &rcp, recipients)
{
CScript scriptPubKey;
scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
vecSend.push_back(make_pair(scriptPubKey, rcp.amount));
}
CWalletTx wtx;
CReserveKey keyChange(wallet); CReserveKey keyChange(wallet);
int64 nFeeRequired = 0; int64 nFeeRequired = 0;
std::string strFailReason; std::string strFailReason;
CWalletTx wtx;
bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason); bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason);
if(!fCreated) if(!fCreated)
@ -194,6 +214,18 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
CClientUIInterface::MSG_ERROR); CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed; return TransactionCreationFailed;
} }
// Store PaymentRequests in wtx.vOrderForm in wallet.
foreach(const SendCoinsRecipient &rcp, recipients)
{
if (rcp.paymentRequest.IsInitialized())
{
std::string key("PaymentRequest");
std::string value;
rcp.paymentRequest.SerializeToString(&value);
wtx.vOrderForm.push_back(make_pair(key, value));
}
}
if(!uiInterface.ThreadSafeAskFee(nFeeRequired)) if(!uiInterface.ThreadSafeAskFee(nFeeRequired))
{ {
return Aborted; return Aborted;
@ -202,10 +234,15 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
{ {
return TransactionCommitFailed; return TransactionCommitFailed;
} }
hex = QString::fromStdString(wtx.GetHash().GetHex());
CTransaction* t = (CTransaction*)&wtx;
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << *t;
transaction.append(&(ssTx[0]), ssTx.size());
} }
// Add addresses / update labels that we've sent to to the address book // Add addresses / update labels that we've sent to to the address book,
// and emit coinsSent signal
foreach(const SendCoinsRecipient &rcp, recipients) foreach(const SendCoinsRecipient &rcp, recipients)
{ {
std::string strAddress = rcp.address.toStdString(); std::string strAddress = rcp.address.toStdString();
@ -214,17 +251,22 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
std::map<CTxDestination, std::string>::iterator mi = wallet->mapAddressBook.find(dest); std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(dest);
// Check if we have a new address or an updated label // Check if we have a new address or an updated label
if (mi == wallet->mapAddressBook.end() || mi->second != strLabel) if (mi == wallet->mapAddressBook.end())
{
wallet->SetAddressBook(dest, strLabel, "send");
}
else if (mi->second.name != strLabel)
{ {
wallet->SetAddressBookName(dest, strLabel); wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose
} }
} }
emit coinsSent(wallet, rcp, transaction);
} }
return SendCoinsReturn(OK, 0, hex); return SendCoinsReturn(OK, 0);
} }
OptionsModel *WalletModel::getOptionsModel() OptionsModel *WalletModel::getOptionsModel()

16
src/qt/walletmodel.h

@ -4,6 +4,7 @@
#include <QObject> #include <QObject>
#include "allocators.h" /* for SecureString */ #include "allocators.h" /* for SecureString */
#include "paymentrequestplus.h"
class OptionsModel; class OptionsModel;
class AddressTableModel; class AddressTableModel;
@ -17,9 +18,15 @@ QT_END_NAMESPACE
class SendCoinsRecipient class SendCoinsRecipient
{ {
public: public:
SendCoinsRecipient() : amount(0) { }
QString address; QString address;
QString label; QString label;
qint64 amount; qint64 amount;
// If from a payment request, paymentRequest.IsInitialized() will be true
PaymentRequestPlus paymentRequest;
QString authenticatedMerchant; // Empty if no authentication or invalid signature/cert/etc.
}; };
/** Interface to Bitcoin wallet from Qt view code. */ /** Interface to Bitcoin wallet from Qt view code. */
@ -68,12 +75,10 @@ public:
struct SendCoinsReturn struct SendCoinsReturn
{ {
SendCoinsReturn(StatusCode status, SendCoinsReturn(StatusCode status,
qint64 fee=0, qint64 fee=0):
QString hex=QString()): status(status), fee(fee) {}
status(status), fee(fee), hex(hex) {}
StatusCode status; StatusCode status;
qint64 fee; // is used in case status is "AmountWithFeeExceedsBalance" qint64 fee; // is used in case status is "AmountWithFeeExceedsBalance"
QString hex; // is filled with the transaction hash if status is "OK"
}; };
// Send coins to a list of recipients // Send coins to a list of recipients
@ -151,6 +156,9 @@ signals:
// Asynchronous message notification // Asynchronous message notification
void message(const QString &title, const QString &message, unsigned int style); void message(const QString &title, const QString &message, unsigned int style);
// Coins sent: from wallet, to recipient, in (serialized) transaction:
void coinsSent(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction);
public slots: public slots:
/* Wallet status might have changed */ /* Wallet status might have changed */
void updateStatus(); void updateStatus();

4
src/qt/walletstack.cpp

@ -54,12 +54,12 @@ void WalletStack::removeAllWallets()
mapWalletViews.clear(); mapWalletViews.clear();
} }
bool WalletStack::handleURI(const QString &uri) bool WalletStack::handlePaymentRequest(const SendCoinsRecipient &recipient)
{ {
WalletView *walletView = (WalletView*)currentWidget(); WalletView *walletView = (WalletView*)currentWidget();
if (!walletView) return false; if (!walletView) return false;
return walletView->handleURI(uri); return walletView->handlePaymentRequest(recipient);
} }
void WalletStack::showOutOfSyncWarning(bool fShow) void WalletStack::showOutOfSyncWarning(bool fShow)

3
src/qt/walletstack.h

@ -20,6 +20,7 @@ class TransactionView;
class OverviewPage; class OverviewPage;
class AddressBookPage; class AddressBookPage;
class SendCoinsDialog; class SendCoinsDialog;
class SendCoinsRecipient;
class SignVerifyMessageDialog; class SignVerifyMessageDialog;
class Notificator; class Notificator;
class RPCConsole; class RPCConsole;
@ -54,7 +55,7 @@ public:
void removeAllWallets(); void removeAllWallets();
bool handleURI(const QString &uri); bool handlePaymentRequest(const SendCoinsRecipient &recipient);
void showOutOfSyncWarning(bool fShow); void showOutOfSyncWarning(bool fShow);

4
src/qt/walletview.cpp

@ -201,10 +201,10 @@ void WalletView::gotoVerifyMessageTab(QString addr)
signVerifyMessageDialog->setAddress_VM(addr); signVerifyMessageDialog->setAddress_VM(addr);
} }
bool WalletView::handleURI(const QString& strURI) bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient)
{ {
// URI has to be valid // URI has to be valid
if (sendCoinsPage->handleURI(strURI)) if (sendCoinsPage->handlePaymentRequest(recipient))
{ {
gotoSendCoinsPage(); gotoSendCoinsPage();
return true; return true;

3
src/qt/walletview.h

@ -16,6 +16,7 @@ class TransactionView;
class OverviewPage; class OverviewPage;
class AddressBookPage; class AddressBookPage;
class SendCoinsDialog; class SendCoinsDialog;
class SendCoinsRecipient;
class SignVerifyMessageDialog; class SignVerifyMessageDialog;
class RPCConsole; class RPCConsole;
@ -49,7 +50,7 @@ public:
*/ */
void setWalletModel(WalletModel *walletModel); void setWalletModel(WalletModel *walletModel);
bool handleURI(const QString &uri); bool handlePaymentRequest(const SendCoinsRecipient& recipient);
void showOutOfSyncWarning(bool fShow); void showOutOfSyncWarning(bool fShow);

6
src/rpcdump.cpp

@ -94,7 +94,7 @@ Value importprivkey(const Array& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->MarkDirty(); pwalletMain->MarkDirty();
pwalletMain->SetAddressBookName(vchAddress, strLabel); pwalletMain->SetAddressBook(vchAddress, strLabel, "receive");
// Don't throw error in case a key is already there // Don't throw error in case a key is already there
if (pwalletMain->HaveKey(vchAddress)) if (pwalletMain->HaveKey(vchAddress))
@ -172,7 +172,7 @@ Value importwallet(const Array& params, bool fHelp)
} }
pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime; pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime;
if (fLabel) if (fLabel)
pwalletMain->SetAddressBookName(keyid, strLabel); pwalletMain->SetAddressBook(keyid, strLabel, "receive");
nTimeBegin = std::min(nTimeBegin, nTime); nTimeBegin = std::min(nTimeBegin, nTime);
} }
file.close(); file.close();
@ -255,7 +255,7 @@ Value dumpwallet(const Array& params, bool fHelp)
CKey key; CKey key;
if (pwalletMain->GetKey(keyid, key)) { if (pwalletMain->GetKey(keyid, key)) {
if (pwalletMain->mapAddressBook.count(keyid)) { if (pwalletMain->mapAddressBook.count(keyid)) {
file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), EncodeDumpString(pwalletMain->mapAddressBook[keyid]).c_str(), strAddr.c_str()); file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), EncodeDumpString(pwalletMain->mapAddressBook[keyid].name).c_str(), strAddr.c_str());
} else if (setKeyPool.count(keyid)) { } else if (setKeyPool.count(keyid)) {
file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), strAddr.c_str()); file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), strAddr.c_str());
} else { } else {

2
src/rpcrawtransaction.cpp

@ -229,7 +229,7 @@ Value listunspent(const Array& params, bool fHelp)
{ {
entry.push_back(Pair("address", CBitcoinAddress(address).ToString())); entry.push_back(Pair("address", CBitcoinAddress(address).ToString()));
if (pwalletMain->mapAddressBook.count(address)) if (pwalletMain->mapAddressBook.count(address))
entry.push_back(Pair("account", pwalletMain->mapAddressBook[address])); entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name));
} }
entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end()))); entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end())));
if (pk.IsPayToScriptHash()) if (pk.IsPayToScriptHash())

50
src/rpcwallet.cpp

@ -114,7 +114,7 @@ Value getnewaddress(const Array& params, bool fHelp)
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
CKeyID keyID = newKey.GetID(); CKeyID keyID = newKey.GetID();
pwalletMain->SetAddressBookName(keyID, strAccount); pwalletMain->SetAddressBook(keyID, strAccount, "receive");
return CBitcoinAddress(keyID).ToString(); return CBitcoinAddress(keyID).ToString();
} }
@ -151,7 +151,7 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false)
if (!pwalletMain->GetKeyFromPool(account.vchPubKey, false)) if (!pwalletMain->GetKeyFromPool(account.vchPubKey, false))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
pwalletMain->SetAddressBookName(account.vchPubKey.GetID(), strAccount); pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
walletdb.WriteAccount(strAccount, account); walletdb.WriteAccount(strAccount, account);
} }
@ -196,12 +196,12 @@ Value setaccount(const Array& params, bool fHelp)
// Detect when changing the account of an address that is the 'unused current key' of another account: // Detect when changing the account of an address that is the 'unused current key' of another account:
if (pwalletMain->mapAddressBook.count(address.Get())) if (pwalletMain->mapAddressBook.count(address.Get()))
{ {
string strOldAccount = pwalletMain->mapAddressBook[address.Get()]; string strOldAccount = pwalletMain->mapAddressBook[address.Get()].name;
if (address == GetAccountAddress(strOldAccount)) if (address == GetAccountAddress(strOldAccount))
GetAccountAddress(strOldAccount, true); GetAccountAddress(strOldAccount, true);
} }
pwalletMain->SetAddressBookName(address.Get(), strAccount); pwalletMain->SetAddressBook(address.Get(), strAccount, "receive");
return Value::null; return Value::null;
} }
@ -219,9 +219,9 @@ Value getaccount(const Array& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
string strAccount; string strAccount;
map<CTxDestination, string>::iterator mi = pwalletMain->mapAddressBook.find(address.Get()); map<CTxDestination, CAddressBookData>::iterator mi = pwalletMain->mapAddressBook.find(address.Get());
if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.empty()) if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.name.empty())
strAccount = (*mi).second; strAccount = (*mi).second.name;
return strAccount; return strAccount;
} }
@ -237,10 +237,10 @@ Value getaddressesbyaccount(const Array& params, bool fHelp)
// Find all addresses that have the given account // Find all addresses that have the given account
Array ret; Array ret;
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook) BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
{ {
const CBitcoinAddress& address = item.first; const CBitcoinAddress& address = item.first;
const string& strName = item.second; const string& strName = item.second.name;
if (strName == strAccount) if (strName == strAccount)
ret.push_back(address.ToString()); ret.push_back(address.ToString());
} }
@ -301,7 +301,7 @@ Value listaddressgroupings(const Array& params, bool fHelp)
{ {
LOCK(pwalletMain->cs_wallet); LOCK(pwalletMain->cs_wallet);
if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end()) if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end())
addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second); addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second.name);
} }
jsonGrouping.push_back(addressInfo); jsonGrouping.push_back(addressInfo);
} }
@ -421,17 +421,6 @@ Value getreceivedbyaddress(const Array& params, bool fHelp)
} }
void GetAccountAddresses(string strAccount, set<CTxDestination>& setAddress)
{
BOOST_FOREACH(const PAIRTYPE(CTxDestination, string)& item, pwalletMain->mapAddressBook)
{
const CTxDestination& address = item.first;
const string& strName = item.second;
if (strName == strAccount)
setAddress.insert(address);
}
}
Value getreceivedbyaccount(const Array& params, bool fHelp) Value getreceivedbyaccount(const Array& params, bool fHelp)
{ {
if (fHelp || params.size() < 1 || params.size() > 2) if (fHelp || params.size() < 1 || params.size() > 2)
@ -446,8 +435,7 @@ Value getreceivedbyaccount(const Array& params, bool fHelp)
// Get the set of pub keys assigned to account // Get the set of pub keys assigned to account
string strAccount = AccountFromValue(params[0]); string strAccount = AccountFromValue(params[0]);
set<CTxDestination> setAddress; set<CTxDestination> setAddress = pwalletMain->GetAccountAddresses(strAccount);
GetAccountAddresses(strAccount, setAddress);
// Tally // Tally
int64 nAmount = 0; int64 nAmount = 0;
@ -780,7 +768,7 @@ Value addmultisigaddress(const Array& params, bool fHelp)
CScriptID innerID = inner.GetID(); CScriptID innerID = inner.GetID();
pwalletMain->AddCScript(inner); pwalletMain->AddCScript(inner);
pwalletMain->SetAddressBookName(innerID, strAccount); pwalletMain->SetAddressBook(innerID, strAccount, "send");
return CBitcoinAddress(innerID).ToString(); return CBitcoinAddress(innerID).ToString();
} }
@ -862,10 +850,10 @@ Value ListReceived(const Array& params, bool fByAccounts)
// Reply // Reply
Array ret; Array ret;
map<string, tallyitem> mapAccountTally; map<string, tallyitem> mapAccountTally;
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, string)& item, pwalletMain->mapAddressBook) BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
{ {
const CBitcoinAddress& address = item.first; const CBitcoinAddress& address = item.first;
const string& strAccount = item.second; const string& strAccount = item.second.name;
map<CBitcoinAddress, tallyitem>::iterator it = mapTally.find(address); map<CBitcoinAddress, tallyitem>::iterator it = mapTally.find(address);
if (it == mapTally.end() && !fIncludeEmpty) if (it == mapTally.end() && !fIncludeEmpty)
continue; continue;
@ -988,7 +976,7 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
{ {
string account; string account;
if (pwalletMain->mapAddressBook.count(r.first)) if (pwalletMain->mapAddressBook.count(r.first))
account = pwalletMain->mapAddressBook[r.first]; account = pwalletMain->mapAddressBook[r.first].name;
if (fAllAccounts || (account == strAccount)) if (fAllAccounts || (account == strAccount))
{ {
Object entry; Object entry;
@ -1101,9 +1089,9 @@ Value listaccounts(const Array& params, bool fHelp)
nMinDepth = params[0].get_int(); nMinDepth = params[0].get_int();
map<string, int64> mapAccountBalances; map<string, int64> mapAccountBalances;
BOOST_FOREACH(const PAIRTYPE(CTxDestination, string)& entry, pwalletMain->mapAddressBook) { BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& entry, pwalletMain->mapAddressBook) {
if (IsMine(*pwalletMain, entry.first)) // This address belongs to me if (IsMine(*pwalletMain, entry.first)) // This address belongs to me
mapAccountBalances[entry.second] = 0; mapAccountBalances[entry.second.name] = 0;
} }
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
@ -1121,7 +1109,7 @@ Value listaccounts(const Array& params, bool fHelp)
{ {
BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& r, listReceived) BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64)& r, listReceived)
if (pwalletMain->mapAddressBook.count(r.first)) if (pwalletMain->mapAddressBook.count(r.first))
mapAccountBalances[pwalletMain->mapAddressBook[r.first]] += r.second; mapAccountBalances[pwalletMain->mapAddressBook[r.first].name] += r.second;
else else
mapAccountBalances[""] += r.second; mapAccountBalances[""] += r.second;
} }
@ -1470,7 +1458,7 @@ Value validateaddress(const Array& params, bool fHelp)
ret.insert(ret.end(), detail.begin(), detail.end()); ret.insert(ret.end(), detail.begin(), detail.end());
} }
if (pwalletMain->mapAddressBook.count(dest)) if (pwalletMain->mapAddressBook.count(dest))
ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest])); ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name));
} }
return ret; return ret;
} }

29
src/util.cpp

@ -8,6 +8,7 @@
#ifdef __linux__ #ifdef __linux__
#define _POSIX_C_SOURCE 200112L #define _POSIX_C_SOURCE 200112L
#endif #endif
#include <algorithm>
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/resource.h> #include <sys/resource.h>
@ -83,7 +84,6 @@ bool fNoListen = false;
bool fLogTimestamps = false; bool fLogTimestamps = false;
CMedianFilter<int64> vTimeOffsets(200,0); CMedianFilter<int64> vTimeOffsets(200,0);
volatile bool fReopenDebugLog = false; volatile bool fReopenDebugLog = false;
bool fCachedPath[2] = {false, false};
// Init OpenSSL library multithreading support // Init OpenSSL library multithreading support
static CCriticalSection** ppmutexOpenSSL; static CCriticalSection** ppmutexOpenSSL;
@ -1043,22 +1043,25 @@ boost::filesystem::path GetDefaultDataDir()
#endif #endif
} }
static boost::filesystem::path pathCached[CChainParams::MAX_NETWORK_TYPES+1];
static CCriticalSection csPathCached;
const boost::filesystem::path &GetDataDir(bool fNetSpecific) const boost::filesystem::path &GetDataDir(bool fNetSpecific)
{ {
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
static fs::path pathCached[2]; LOCK(csPathCached);
static CCriticalSection csPathCached;
int nNet = CChainParams::MAX_NETWORK_TYPES;
if (fNetSpecific) nNet = Params().NetworkID();
fs::path &path = pathCached[fNetSpecific]; fs::path &path = pathCached[nNet];
// This can be called during exceptions by printf, so we cache the // This can be called during exceptions by printf, so we cache the
// value so we don't have to do memory allocations after that. // value so we don't have to do memory allocations after that.
if (fCachedPath[fNetSpecific]) if (!path.empty())
return path; return path;
LOCK(csPathCached);
if (mapArgs.count("-datadir")) { if (mapArgs.count("-datadir")) {
path = fs::system_complete(mapArgs["-datadir"]); path = fs::system_complete(mapArgs["-datadir"]);
if (!fs::is_directory(path)) { if (!fs::is_directory(path)) {
@ -1073,10 +1076,15 @@ const boost::filesystem::path &GetDataDir(bool fNetSpecific)
fs::create_directories(path); fs::create_directories(path);
fCachedPath[fNetSpecific] = true;
return path; return path;
} }
void ClearDatadirCache()
{
std::fill(&pathCached[0], &pathCached[CChainParams::MAX_NETWORK_TYPES+1],
boost::filesystem::path());
}
boost::filesystem::path GetConfigFile() boost::filesystem::path GetConfigFile()
{ {
boost::filesystem::path pathConfigFile(GetArg("-conf", "bitcoin.conf")); boost::filesystem::path pathConfigFile(GetArg("-conf", "bitcoin.conf"));
@ -1091,9 +1099,6 @@ void ReadConfigFile(map<string, string>& mapSettingsRet,
if (!streamConfig.good()) if (!streamConfig.good())
return; // No bitcoin.conf file is OK return; // No bitcoin.conf file is OK
// clear path cache after loading config file
fCachedPath[0] = fCachedPath[1] = false;
set<string> setOptions; set<string> setOptions;
setOptions.insert("*"); setOptions.insert("*");
@ -1109,6 +1114,8 @@ void ReadConfigFile(map<string, string>& mapSettingsRet,
} }
mapMultiSettingsRet[strKey].push_back(it->value[0]); mapMultiSettingsRet[strKey].push_back(it->value[0]);
} }
// If datadir is changed in .conf file:
ClearDatadirCache();
} }
boost::filesystem::path GetPidFile() boost::filesystem::path GetPidFile()

31
src/wallet.cpp

@ -496,7 +496,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn)
if (GetKeyFromPool(newDefaultKey, false)) if (GetKeyFromPool(newDefaultKey, false))
{ {
SetDefaultKey(newDefaultKey); SetDefaultKey(newDefaultKey);
SetAddressBookName(vchDefaultKey.GetID(), ""); SetAddressBook(vchDefaultKey.GetID(), "", "receive");
} }
} }
} }
@ -717,8 +717,8 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nReceived,
{ {
if (pwallet->mapAddressBook.count(r.first)) if (pwallet->mapAddressBook.count(r.first))
{ {
map<CTxDestination, string>::const_iterator mi = pwallet->mapAddressBook.find(r.first); map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.first);
if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount) if (mi != pwallet->mapAddressBook.end() && (*mi).second.name == strAccount)
nReceived += r.second; nReceived += r.second;
} }
else if (strAccount.empty()) else if (strAccount.empty())
@ -1457,26 +1457,28 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
} }
bool CWallet::SetAddressBookName(const CTxDestination& address, const string& strName) bool CWallet::SetAddressBook(const CTxDestination& address, const string& strName, const string& strPurpose)
{ {
std::map<CTxDestination, std::string>::iterator mi = mapAddressBook.find(address); std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address);
mapAddressBook[address] = strName; mapAddressBook[address].name = strName;
NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address), (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED); NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address), (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED);
if (!fFileBacked) if (!fFileBacked)
return false; return false;
if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose))
return false;
return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName); return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName);
} }
bool CWallet::DelAddressBookName(const CTxDestination& address) bool CWallet::DelAddressBook(const CTxDestination& address)
{ {
mapAddressBook.erase(address); mapAddressBook.erase(address);
NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address), CT_DELETED); NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address), CT_DELETED);
if (!fFileBacked) if (!fFileBacked)
return false; return false;
CWalletDB(strWalletFile).ErasePurpose(CBitcoinAddress(address).ToString());
return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString()); return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString());
} }
void CWallet::PrintWallet(const CBlock& block) void CWallet::PrintWallet(const CBlock& block)
{ {
{ {
@ -1812,6 +1814,19 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings()
return ret; return ret;
} }
set<CTxDestination> CWallet::GetAccountAddresses(string strAccount) const
{
set<CTxDestination> result;
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, mapAddressBook)
{
const CTxDestination& address = item.first;
const string& strName = item.second.name;
if (strName == strAccount)
result.insert(address);
}
return result;
}
bool CReserveKey::GetReservedKey(CPubKey& pubkey) bool CReserveKey::GetReservedKey(CPubKey& pubkey)
{ {
if (nIndex == -1) if (nIndex == -1)

21
src/wallet.h

@ -64,6 +64,19 @@ public:
) )
}; };
/** Address book data */
class CAddressBookData
{
public:
std::string name;
std::string purpose;
CAddressBookData()
{
purpose = "unknown";
}
};
/** A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, /** A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
* and provides the ability to create new transactions. * and provides the ability to create new transactions.
*/ */
@ -124,7 +137,7 @@ public:
int64 nOrderPosNext; int64 nOrderPosNext;
std::map<uint256, int> mapRequestCount; std::map<uint256, int> mapRequestCount;
std::map<CTxDestination, std::string> mapAddressBook; std::map<CTxDestination, CAddressBookData> mapAddressBook;
CPubKey vchDefaultKey; CPubKey vchDefaultKey;
@ -214,6 +227,8 @@ public:
std::set< std::set<CTxDestination> > GetAddressGroupings(); std::set< std::set<CTxDestination> > GetAddressGroupings();
std::map<CTxDestination, int64> GetAddressBalances(); std::map<CTxDestination, int64> GetAddressBalances();
std::set<CTxDestination> GetAccountAddresses(std::string strAccount) const;
bool IsMine(const CTxIn& txin) const; bool IsMine(const CTxIn& txin) const;
int64 GetDebit(const CTxIn& txin) const; int64 GetDebit(const CTxIn& txin) const;
bool IsMine(const CTxOut& txout) const bool IsMine(const CTxOut& txout) const
@ -281,9 +296,9 @@ public:
DBErrors LoadWallet(bool& fFirstRunRet); DBErrors LoadWallet(bool& fFirstRunRet);
bool SetAddressBookName(const CTxDestination& address, const std::string& strName); bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose);
bool DelAddressBookName(const CTxDestination& address); bool DelAddressBook(const CTxDestination& address);
void UpdatedTransaction(const uint256 &hashTx); void UpdatedTransaction(const uint256 &hashTx);

20
src/walletdb.cpp

@ -32,6 +32,18 @@ bool CWalletDB::EraseName(const string& strAddress)
return Erase(make_pair(string("name"), strAddress)); return Erase(make_pair(string("name"), strAddress));
} }
bool CWalletDB::WritePurpose(const string& strAddress, const string& strPurpose)
{
nWalletDBUpdated++;
return Write(make_pair(string("purpose"), strAddress), strPurpose);
}
bool CWalletDB::ErasePurpose(const string& strPurpose)
{
nWalletDBUpdated++;
return Erase(make_pair(string("purpose"), strPurpose));
}
bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account) bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
{ {
account.SetNull(); account.SetNull();
@ -212,7 +224,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
{ {
string strAddress; string strAddress;
ssKey >> strAddress; ssKey >> strAddress;
ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()]; ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].name;
}
else if (strType == "purpose")
{
string strAddress;
ssKey >> strAddress;
ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].purpose;
} }
else if (strType == "tx") else if (strType == "tx")
{ {

4
src/walletdb.h

@ -68,9 +68,11 @@ private:
void operator=(const CWalletDB&); void operator=(const CWalletDB&);
public: public:
bool WriteName(const std::string& strAddress, const std::string& strName); bool WriteName(const std::string& strAddress, const std::string& strName);
bool EraseName(const std::string& strAddress); bool EraseName(const std::string& strAddress);
bool WritePurpose(const std::string& strAddress, const std::string& purpose);
bool ErasePurpose(const std::string& strAddress);
bool WriteTx(uint256 hash, const CWalletTx& wtx) bool WriteTx(uint256 hash, const CWalletTx& wtx)
{ {
nWalletDBUpdated++; nWalletDBUpdated++;

Loading…
Cancel
Save