diff --git a/include/gigamonkey/SPV.hpp b/include/gigamonkey/SPV.hpp index faaa2ab..6647f53 100644 --- a/include/gigamonkey/SPV.hpp +++ b/include/gigamonkey/SPV.hpp @@ -20,8 +20,9 @@ namespace Gigamonkey::SPV { N Height; Bitcoin::header Header; - confirmation () : Path {}, Height {0}, Header {} {} - confirmation (Merkle::path p, const N &height, const Bitcoin::header &h): Path {p}, Height {height}, Header {h} {} + confirmation (); + confirmation (Merkle::path p, const N &height, const Bitcoin::header &h); + bool operator == (const confirmation &t) const; std::strong_ordering operator <=> (const confirmation &t) const; @@ -44,33 +45,36 @@ namespace Gigamonkey::SPV { // a transaction that has been accepted by the network. struct accepted : ptr { accepted (); - accepted (ptr &&); - accepted (const ptr &); + accepted (ptr &&n); + accepted (const ptr &n); + bool valid () const; - explicit operator bool (); bool operator == (const accepted &tx) const; - std::partial_ordering operator <=> (const accepted &tx); }; struct map : tool::base_rb_map { using tool::base_rb_map::base_rb_map; bool operator == (const map &) const; - std::partial_ordering operator <=> (const map &) const; + bool contains_branch (const Bitcoin::TXID &); }; // an spv proof is a tree whose nodes are txs and whose leaves are all Merkle proofs. - struct tree : either { - using either::either; - std::partial_ordering operator <=> (const tree &t) const; + struct tree : either { + using either::either; bool valid () const; }; + // establish partial ordering of transactions. + static std::partial_ordering ordering (const entry &a, const entry &b); + struct node { Bitcoin::transaction Transaction; tree Proof; - node (const Bitcoin::transaction &tx, const confirmation &c) : Transaction {tx}, Proof {c} {} - node (const Bitcoin::transaction &tx, map m) : Transaction {tx}, Proof {m} {} + node (const Bitcoin::transaction &tx, const confirmation &c); + node (const Bitcoin::transaction &tx, map m); + + bool operator == (const node &) const; }; // the payment is in these transactions. @@ -93,12 +97,6 @@ namespace Gigamonkey::SPV { }; - // attempt to generate a given SPV proof for an unconfirmed transaction. - // this proof can be sent to a merchant who can use it to confirm that - // the transaction is valid. - maybe generate_proof (database &d, list payment); - maybe extend (database &d, Bitcoin::transaction); - // convert proofs and proof parts to extended transactions. list inline extended_transactions (list payment, proof::map proof) { return for_each ([proof] (const Bitcoin::transaction &tx) -> extended::transaction { @@ -114,6 +112,12 @@ namespace Gigamonkey::SPV { }, tx.Inputs), tx.Outputs, tx.LockTime}; } + // attempt to generate a given SPV proof for an unconfirmed transaction. + // this proof can be sent to a merchant who can use it to confirm that + // the transaction is valid. + maybe generate_proof (database &d, list payment); + maybe extend (database &d, Bitcoin::transaction); + struct database { // get a block header by height. @@ -126,11 +130,12 @@ namespace Gigamonkey::SPV { // a transaction in the database, which may include a merkle proof if we have one. struct tx { + ptr Transaction; confirmation Confirmation; - tx (ptr t, const confirmation &x) : Transaction {t}, Confirmation {x} {} - tx (ptr t) : Transaction {t}, Confirmation {} {} + tx (ptr t, const confirmation &x); + tx (ptr t); bool valid () const; // whether a proof is included. @@ -170,7 +175,7 @@ namespace Gigamonkey::SPV { virtual ~writable () {} }; - struct database::memory : virtual database, virtual writable { + struct database::memory : public virtual database, public virtual writable { struct entry { data::entry Header; Merkle::map Paths; @@ -180,13 +185,9 @@ namespace Gigamonkey::SPV { entry (data::N n, Bitcoin::header h, Merkle::map tree) : Header {n, h}, Paths {tree}, Last {nullptr} {} entry (Bitcoin::header h, const Merkle::BUMP &bump) : Header {bump.BlockHeight, h}, Paths {bump.paths ()}, Last {nullptr} {} - Merkle::dual dual_tree () const { - return Merkle::dual {Paths, Header.Value.MerkleRoot}; - } + Merkle::dual dual_tree () const; - Merkle::BUMP BUMP () const { - return Merkle::BUMP {uint64 (Header.Key), Paths}; - } + Merkle::BUMP BUMP () const; }; ptr Latest; @@ -227,13 +228,8 @@ namespace Gigamonkey::SPV { }; - bool inline proof::valid (const Bitcoin::transaction &tx, const Merkle::path &p, const Bitcoin::header &h) { - return h.valid () && valid (tx.id (), p, h.MerkleRoot); - } - - bool inline proof::valid (const Bitcoin::TXID &id, const Merkle::path &p, const digest256 &root) { - return p.derive_root (id) == root; - } + inline confirmation::confirmation (): Path {}, Height {0}, Header {} {} + inline confirmation::confirmation (Merkle::path p, const N &height, const Bitcoin::header &h): Path {p}, Height {height}, Header {h} {} bool inline confirmation::operator == (const confirmation &t) const { return Header == t.Header && t.Height == Height && Path.Index == t.Path.Index; @@ -244,6 +240,18 @@ namespace Gigamonkey::SPV { return cmp_block == std::strong_ordering::equal ? cmp_block : Path.Index <=> t.Path.Index; } + bool inline confirmation::valid () const { + return Header.Timestamp != Bitcoin::timestamp {0}; + } + + bool inline proof::valid (const Bitcoin::transaction &tx, const Merkle::path &p, const Bitcoin::header &h) { + return h.valid () && valid (tx.id (), p, h.MerkleRoot); + } + + bool inline proof::valid (const Bitcoin::TXID &id, const Merkle::path &p, const digest256 &root) { + return p.derive_root (id) == root; + } + set inline database::memory::unconfirmed () { return Pending; } @@ -252,6 +260,31 @@ namespace Gigamonkey::SPV { return extended_transactions (Payment, Proof); } + inline proof::accepted::accepted (): ptr {nullptr} {} + inline proof::accepted::accepted (ptr &&n): ptr {n} {} + inline proof::accepted::accepted (const ptr &n): ptr {n} {} + + bool inline proof::accepted::valid () const { + return static_cast> (*this) != nullptr; + } + + bool inline proof::accepted::operator == (const accepted &tx) const { + if (static_cast> (*this) == static_cast> (tx)) return true; + return *(*this) == *tx; + } + + inline proof::node::node (const Bitcoin::transaction &tx, const confirmation &c) : Transaction {tx}, Proof {c} {} + + inline proof::node::node (const Bitcoin::transaction &tx, map m) : Transaction {tx}, Proof {m} {} + + bool inline proof::tree::valid () const { + return this->is () && !data::empty (this->get ()) || + this->is () && this->get ().valid (); + } + + inline database::tx::tx (ptr t, const confirmation &x) : Transaction {t}, Confirmation {x} {} + inline database::tx::tx (ptr t) : Transaction {t}, Confirmation {} {} + bool inline database::tx::validate () const { if (!confirmed ()) return false; return proof::valid (*Transaction, Confirmation.Path, Confirmation.Header); @@ -265,14 +298,13 @@ namespace Gigamonkey::SPV { return Transaction != nullptr; } - inline proof::accepted::accepted (): ptr {nullptr} {} - inline proof::accepted::accepted (ptr &&n): ptr {n} {} - inline proof::accepted::accepted (const ptr &n): ptr {n} {} + Merkle::dual inline database::memory::entry::dual_tree () const { + return Merkle::dual {Paths, Header.Value.MerkleRoot}; + } - bool inline confirmation::valid () const { - return Header.Timestamp != Bitcoin::timestamp {0}; + Merkle::BUMP inline database::memory::entry::BUMP () const { + return Merkle::BUMP {uint64 (Header.Key), Paths}; } - } #endif diff --git a/src/gigamonkey/SPV.cpp b/src/gigamonkey/SPV.cpp index 16bac0b..8f3f880 100644 --- a/src/gigamonkey/SPV.cpp +++ b/src/gigamonkey/SPV.cpp @@ -225,4 +225,31 @@ namespace Gigamonkey::SPV { Pending = Pending.remove (txid); Transactions.erase (txid); } + + std::partial_ordering SPV::proof::ordering (const data::entry &a, const data::entry &b) { + if (a.Key == b.Key) return std::partial_ordering::equivalent; + + if (a.Value.is ()) { + return b.Value.is () ? + a.Value.get () <=> b.Value.get (): + std::partial_ordering::less; + } + + if (b.Value.is ()) return std::partial_ordering::greater; + + if (b.Value.get ().contains_branch (a.Key)) return std::partial_ordering::less; + if (a.Value.get ().contains_branch (b.Key)) return std::partial_ordering::greater; + + return std::partial_ordering::unordered; + } + + bool proof::map::contains_branch (const Bitcoin::TXID &txid) { + for (const auto &[k, v] : *this) + if (k == txid || + v->Proof.is () && + v->Proof.get ().contains_branch (txid)) return true; + + return false; + } + } diff --git a/src/gigamonkey/pay/SPV_envelope.cpp b/src/gigamonkey/pay/SPV_envelope.cpp index d5cb418..7d50721 100644 --- a/src/gigamonkey/pay/SPV_envelope.cpp +++ b/src/gigamonkey/pay/SPV_envelope.cpp @@ -229,8 +229,8 @@ namespace Gigamonkey { } namespace { - SPV::proof::map read_SPV_proof (map inputs, const SPV::database &db); - SPV::proof::map read_SPV_proof (map> inputs, const SPV::database &db); + SPV::proof::map read_SPV_proof (map inputs, SPV::database &db); + SPV::proof::map read_SPV_proof (map> inputs, SPV::database &db); } SPV::proof SPV_envelope::read_SPV_proof (SPV::database &db) const {