Browse Source

Added mapAnchors consensus rules, finished zcrawpour/zcrawreceive.

Some specifics on consensus changes:
* Transactions must be anchored to a real anchor in the chain.
* Anchors are pushed and popped during ConnectBlock/DisconnectBlock as appropriate.
* DisconnectTip triggers evictions, under some circumstances, of transactions in the
  mempool which are anchored to roots that are no longer valid.
* Commitments append to the tree at the current best root during ConnectBlock.
pull/4/head
Sean Bowe 8 years ago
parent
commit
a8ac403db0
  1. 4
      depends/packages/libzerocash.mk
  2. 31
      qa/rpc-tests/zcpour.py
  3. 15
      src/coins.cpp
  4. 3
      src/coins.h
  5. 63
      src/main.cpp
  6. 1
      src/rpcserver.cpp
  7. 1
      src/rpcserver.h
  8. 26
      src/txmempool.cpp
  9. 1
      src/txmempool.h
  10. 2
      src/undo.h
  11. 115
      src/wallet/rpcwallet.cpp
  12. 62
      src/wallet/wallet.cpp
  13. 1
      src/wallet/wallet.h

4
depends/packages/libzerocash.mk

@ -2,8 +2,8 @@ package=libzerocash
$(package)_download_path=https://github.com/Electric-Coin-Company/$(package)/archive/
$(package)_file_name=$(package)-$($(package)_git_commit).tar.gz
$(package)_download_file=$($(package)_git_commit).tar.gz
$(package)_sha256_hash=8000a2cdc276ab4ee3ad3cbd7361162424ab0c4794f17d425f25bfca46853af8
$(package)_git_commit=1503312b1b340495c9f6a3254587c7fe2c3c87d7
$(package)_sha256_hash=1364a739751bcdda86cfd66d3d019844d116c374d7a7634bfb3e1a47c085f3c0
$(package)_git_commit=dd5db5815be70f0e4895784cc905df6f1c73cb17
$(package)_dependencies=libsnark crypto++ openssl boost libgmp
$(package)_patches=

31
qa/rpc-tests/zcpour.py

@ -16,6 +16,35 @@ class PourTxTest(BitcoinTestFramework):
# Start with split network:
return super(PourTxTest, self).setup_network(True)
def send_pours_around(self):
zckeypair = self.nodes[0].zcrawkeygen()
zcsecretkey = zckeypair["zcsecretkey"]
zcaddress = zckeypair["zcaddress"]
(total_in, inputs) = gather_inputs(self.nodes[1], 50)
protect_tx = self.nodes[1].createrawtransaction(inputs, {})
pour_result = self.nodes[1].zcrawpour(protect_tx, {}, {zcaddress:49.9}, 50, 0.1)
receive_result = self.nodes[1].zcrawreceive(zcsecretkey, pour_result["encryptedbucket1"])
assert_equal(receive_result["exists"], False)
protect_tx = self.nodes[1].signrawtransaction(pour_result["rawtxn"])
self.nodes[1].sendrawtransaction(protect_tx["hex"])
self.nodes[1].generate(1)
receive_result = self.nodes[1].zcrawreceive(zcsecretkey, pour_result["encryptedbucket1"])
assert_equal(receive_result["exists"], True)
pour_tx = self.nodes[1].createrawtransaction([], {})
pour_result = self.nodes[1].zcrawpour(pour_tx, {receive_result["bucket"] : zcsecretkey}, {zcaddress: 49.8}, 0, 0.1)
self.nodes[1].sendrawtransaction(pour_result["rawtxn"])
self.nodes[1].generate(1)
sync_blocks(self.nodes)
receive_result = self.nodes[0].zcrawreceive(zcsecretkey, pour_result["encryptedbucket1"])
assert_equal(receive_result["exists"], True)
def run_test(self):
# All nodes should start with 1,250 BTC:
starting_balance = 1250
@ -66,6 +95,8 @@ class PourTxTest(BitcoinTestFramework):
print signed_tx_pour
self.nodes[0].sendrawtransaction(signed_tx_pour["hex"])
self.send_pours_around()
if __name__ == '__main__':
PourTxTest().main()

15
src/coins.cpp

@ -387,6 +387,21 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
return nResult;
}
bool CCoinsViewCache::HavePourRequirements(const CTransaction& tx) const
{
BOOST_FOREACH(const CPourTx &pour, tx.vpour)
{
libzerocash::IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
if (!GetAnchorAt(pour.anchor, tree)) {
// If we do not have the anchor for the pour,
// it is invalid.
return false;
}
}
return true;
}
bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
{
if (!tx.IsCoinBase()) {

3
src/coins.h

@ -517,6 +517,9 @@ public:
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
bool HaveInputs(const CTransaction& tx) const;
//! Check whether all pour requirements (anchors/serials) are satisfied
bool HavePourRequirements(const CTransaction& tx) const;
//! Return priority of tx at height nHeight
double GetPriority(const CTransaction &tx, int nHeight) const;

63
src/main.cpp

@ -1057,6 +1057,11 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),
REJECT_DUPLICATE, "bad-txns-inputs-spent");
// are the pour's requirements met?
if (!view.HavePourRequirements(tx))
return state.Invalid(error("AcceptToMemoryPool: pour requirements not met"),
REJECT_DUPLICATE, "bad-txns-pour-requirements-not-met");
// Bring the best block into scope
view.GetBestBlock();
@ -1505,6 +1510,10 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
if (!inputs.HaveInputs(tx))
return state.Invalid(error("CheckInputs(): %s inputs unavailable", tx.GetHash().ToString()));
// are the pour's requirements met?
if (!inputs.HavePourRequirements(tx))
return state.Invalid(error("CheckInputs(): %s pour requirements not met", tx.GetHash().ToString()));
// While checking, GetBestBlock() refers to the parent block.
// This is also true for mempool checks.
CBlockIndex *pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second;
@ -1766,6 +1775,9 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
}
}
// set the old best anchor back
view.PopAnchor(blockUndo.old_tree_root);
// move best block pointer to prevout block
view.SetBestBlock(pindex->pprev->GetBlockHash());
@ -1953,6 +1965,25 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
std::vector<std::pair<uint256, CDiskTxPos> > vPos;
vPos.reserve(block.vtx.size());
blockundo.vtxundo.reserve(block.vtx.size() - 1);
// Construct the incremental merkle tree at the current
// block position,
auto old_tree_root = view.GetBestAnchor();
libzerocash::IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
// This should never fail: we should always be able to get the root
// that is on the tip of our chain
assert(view.GetAnchorAt(old_tree_root, tree));
{
// Consistency check: the root of the tree we're given should
// match what we asked for.
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
uint256 anchor_received = uint256(newrt_v);
assert(anchor_received == old_tree_root);
}
for (unsigned int i = 0; i < block.vtx.size(); i++)
{
const CTransaction &tx = block.vtx[i];
@ -1969,6 +2000,11 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
return state.DoS(100, error("ConnectBlock(): inputs missing/spent"),
REJECT_INVALID, "bad-txns-inputs-missingorspent");
// are the pour's requirements met?
if (!view.HavePourRequirements(tx))
return state.DoS(100, error("ConnectBlock(): pour requirements not met"),
REJECT_INVALID, "bad-txns-pour-requirements-not-met");
if (fStrictPayToScriptHash)
{
// Add in sigops done by pay-to-script-hash inputs;
@ -1994,9 +2030,26 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
}
UpdateCoins(tx, state, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
BOOST_FOREACH(const uint256 &bucket_commitment, pour.commitments) {
// Insert the bucket commitments into our temporary tree.
std::vector<bool> index;
std::vector<unsigned char> commitment_value(bucket_commitment.begin(), bucket_commitment.end());
std::vector<bool> commitment_bv(ZC_CM_SIZE * 8);
libzerocash::convertBytesVectorToVector(commitment_value, commitment_bv);
tree.insertElement(commitment_bv, index);
}
}
vPos.push_back(std::make_pair(tx.GetHash(), pos));
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
}
tree.prune(); // prune it, so we don't cache intermediate states we don't need
view.PushAnchor(tree);
blockundo.old_tree_root = old_tree_root;
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
@ -2225,6 +2278,7 @@ bool static DisconnectTip(CValidationState &state) {
if (!ReadBlockFromDisk(block, pindexDelete))
return AbortNode(state, "Failed to read block");
// Apply the block atomically to the chain state.
uint256 anchorBeforeDisconnect = pcoinsTip->GetBestAnchor();
int64_t nStart = GetTimeMicros();
{
CCoinsViewCache view(pcoinsTip);
@ -2233,6 +2287,7 @@ bool static DisconnectTip(CValidationState &state) {
assert(view.Flush());
}
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
uint256 anchorAfterDisconnect = pcoinsTip->GetBestAnchor();
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
return false;
@ -2244,6 +2299,11 @@ bool static DisconnectTip(CValidationState &state) {
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
mempool.remove(tx, removed, true);
}
if (anchorBeforeDisconnect != anchorAfterDisconnect) {
// The anchor may not change between block disconnects,
// in which case we don't want to evict from the mempool yet!
mempool.removeWithAnchor(anchorBeforeDisconnect);
}
mempool.removeCoinbaseSpends(pcoinsTip, pindexDelete->nHeight);
mempool.check(pcoinsTip);
// Update chainActive and related variables.
@ -4536,7 +4596,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
BOOST_FOREACH(uint256 hash, vEraseQueue)
EraseOrphanTx(hash);
}
else if (fMissingInputs)
// TODO: currently, prohibit pours from entering mapOrphans
else if (fMissingInputs && tx.vpour.size() == 0)
{
AddOrphanTx(tx, pfrom->GetId());

1
src/rpcserver.cpp

@ -378,6 +378,7 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "walletpassphrase", &walletpassphrase, true },
{ "wallet", "zcrawkeygen", &zc_raw_keygen, true },
{ "wallet", "zcrawpour", &zc_raw_pour, true },
{ "wallet", "zcrawreceive", &zc_raw_receive, true }
#endif // ENABLE_WALLET
};

1
src/rpcserver.h

@ -210,6 +210,7 @@ extern json_spirit::Value setmocktime(const json_spirit::Array& params, bool fHe
extern json_spirit::Value resendwallettransactions(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value zc_raw_keygen(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value zc_raw_pour(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value zc_raw_receive(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getrawtransaction(const json_spirit::Array& params, bool fHelp); // in rcprawtransaction.cpp
extern json_spirit::Value listunspent(const json_spirit::Array& params, bool fHelp);

26
src/txmempool.cpp

@ -178,6 +178,32 @@ void CTxMemPool::removeCoinbaseSpends(const CCoinsViewCache *pcoins, unsigned in
}
}
void CTxMemPool::removeWithAnchor(const uint256 &invalidRoot)
{
// If a block is disconnected from the tip, and the root changed,
// we must invalidate transactions from the mempool which spend
// from that root -- almost as though they were spending coinbases
// which are no longer valid to spend due to coinbase maturity.
LOCK(cs);
list<CTransaction> transactionsToRemove;
for (std::map<uint256, CTxMemPoolEntry>::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
const CTransaction& tx = it->second.GetTx();
BOOST_FOREACH(const CPourTx& pour, tx.vpour) {
if (pour.anchor == invalidRoot) {
transactionsToRemove.push_back(tx);
break;
}
}
}
BOOST_FOREACH(const CTransaction& tx, transactionsToRemove) {
list<CTransaction> removed;
remove(tx, removed, true);
}
}
void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed)
{
// Remove transactions which depend on inputs of tx, recursively

1
src/txmempool.h

@ -114,6 +114,7 @@ public:
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true);
void remove(const CTransaction &tx, std::list<CTransaction>& removed, bool fRecursive = false);
void removeWithAnchor(const uint256 &invalidRoot);
void removeCoinbaseSpends(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight);
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,

2
src/undo.h

@ -73,12 +73,14 @@ class CBlockUndo
{
public:
std::vector<CTxUndo> vtxundo; // for all but the coinbase
uint256 old_tree_root;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(vtxundo);
READWRITE(old_tree_root);
}
};

115
src/wallet/rpcwallet.cpp

@ -2344,13 +2344,68 @@ Value listunspent(const Array& params, bool fHelp)
return results;
}
Value zc_raw_receive(const json_spirit::Array& params, bool fHelp)
{
/*
zcrawreceive <zcsecretkey> <encrypted_bucket>
*/
RPCTypeCheck(params, boost::assign::list_of(str_type)(str_type));
LOCK(cs_main);
std::vector<unsigned char> a_sk;
std::string sk_enc;
{
CDataStream ssData(ParseHexV(params[0], "zcsecretkey"), SER_NETWORK, PROTOCOL_VERSION);
try {
ssData >> a_sk;
ssData >> sk_enc;
} catch(const std::exception &) {
throw runtime_error(
"zcsecretkey could not be decoded"
);
}
}
libzerocash::PrivateAddress zcsecretkey(a_sk, sk_enc);
libzerocash::Address zcaddress(zcsecretkey);
auto encrypted_bucket_vec = ParseHexV(params[1], "encrypted_bucket");
std::string encrypted_bucket(encrypted_bucket_vec.begin(), encrypted_bucket_vec.end());
libzerocash::Coin decrypted_bucket(encrypted_bucket, zcaddress);
std::vector<unsigned char> commitment_v = decrypted_bucket.getCoinCommitment().getCommitmentValue();
uint256 commitment = uint256(commitment_v);
assert(pwalletMain != NULL);
libsnark::merkle_authentication_path path(INCREMENTAL_MERKLE_TREE_DEPTH); // We don't care during receive... yet! :)
size_t path_index = 0;
uint256 anchor;
auto found_in_chain = pwalletMain->WitnessBucketCommitment(commitment, path, path_index, anchor);
CAmount value_of_bucket = decrypted_bucket.getValue();
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
{
ss << decrypted_bucket.getValue();
ss << decrypted_bucket.getRho();
ss << decrypted_bucket.getR();
}
Object result;
result.push_back(Pair("amount", ValueFromAmount(value_of_bucket)));
result.push_back(Pair("bucket", HexStr(ss.begin(), ss.end())));
result.push_back(Pair("exists", found_in_chain));
return result;
}
Value zc_raw_pour(const json_spirit::Array& params, bool fHelp)
{
/*
zcrawpour <rawtx> {<bucket>: <zcsecretkey>, ...} {<zcaddress>: <value>, ...} vpub_old vpub_new
*/
//RPCTypeCheck(params, boost::assign::list_of(str_type)(obj_type)(obj_type)(int_type)(int_type));
LOCK(cs_main);
CTransaction tx;
if (!DecodeHexTx(tx, params[0].get_str()))
@ -2371,24 +2426,61 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp)
std::vector<PourInput> vpourin;
std::vector<PourOutput> vpourout;
/*
uint256 anchor;
BOOST_FOREACH(const Pair& s, inputs)
{
// TODO
CDataStream ssData(ParseHexV(s.name_, "bucket"), SER_NETWORK, PROTOCOL_VERSION);
uint64_t value;
std::vector<unsigned char> rho;
std::vector<unsigned char> r;
ssData >> value;
ssData >> rho;
ssData >> r;
std::vector<unsigned char> a_sk;
std::string sk_enc;
{
CDataStream ssData2(ParseHexV(s.value_, "zcsecretkey"), SER_NETWORK, PROTOCOL_VERSION);
try {
ssData2 >> a_sk;
ssData2 >> sk_enc;
} catch(const std::exception &) {
throw runtime_error(
"zcsecretkey could not be decoded"
);
}
}
libzerocash::PrivateAddress zcsecretkey(a_sk, sk_enc);
libzerocash::Address zcaddress(zcsecretkey);
libzerocash::Coin input_coin(zcaddress.getPublicAddress(), value, rho, r);
std::vector<unsigned char> commitment_v = input_coin.getCoinCommitment().getCommitmentValue();
uint256 commitment = uint256(commitment_v);
libsnark::merkle_authentication_path path(INCREMENTAL_MERKLE_TREE_DEPTH);
size_t path_index = 0;
assert(pwalletMain != NULL);
if (!pwalletMain->WitnessBucketCommitment(commitment, path, path_index, anchor)) {
throw std::runtime_error("Couldn't find bucket in the blockchain");
}
vpourin.push_back(PourInput(input_coin, zcaddress, path_index, path));
}
*/
// TODO
vpourin.push_back(PourInput(INCREMENTAL_MERKLE_TREE_DEPTH));
vpourin.push_back(PourInput(INCREMENTAL_MERKLE_TREE_DEPTH));
while (vpourin.size() < 2) {
vpourin.push_back(PourInput(INCREMENTAL_MERKLE_TREE_DEPTH));
}
BOOST_FOREACH(const Pair& s, outputs)
{
libzerocash::PublicAddress addrTo;
{
vector<unsigned char> decoded(ParseHex(s.name_));
CDataStream ssData(decoded, SER_NETWORK, PROTOCOL_VERSION);
CDataStream ssData(ParseHexV(s.name_, "to_address"), SER_NETWORK, PROTOCOL_VERSION);
std::vector<unsigned char> pubAddressSecret;
std::string encryptionPublicKey;
@ -2415,7 +2507,6 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp)
throw runtime_error("unsupported");
}
uint256 anchor; // TODO
CScript scriptPubKey;
CPourTx pourtx(*pzerocashParams,
scriptPubKey,
@ -2425,6 +2516,8 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp)
vpub_old,
vpub_new);
assert(pourtx.Verify(*pzerocashParams));
CMutableTransaction mtx(tx);
mtx.nVersion = 2;
mtx.vpour.push_back(pourtx);

62
src/wallet/wallet.cpp

@ -1051,6 +1051,68 @@ bool CWalletTx::WriteToDisk(CWalletDB *pwalletdb)
return pwalletdb->WriteTx(GetHash(), *this);
}
bool CWallet::WitnessBucketCommitment(uint256 &commitment,
libsnark::merkle_authentication_path& path,
size_t &path_index,
uint256 &final_anchor)
{
bool res = false;
std::vector<bool> commitment_index;
CBlockIndex* pindex = chainActive.Genesis();
libzerocash::IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
uint256 current_anchor;
while (pindex) {
CBlock block;
ReadBlockFromDisk(block, pindex);
BOOST_FOREACH(const CTransaction& tx, block.vtx)
{
BOOST_FOREACH(const CPourTx& pour, tx.vpour)
{
BOOST_FOREACH(const uint256 &bucket_commitment, pour.commitments)
{
std::vector<bool> commitment_bv(ZC_CM_SIZE * 8);
std::vector<bool> index;
std::vector<unsigned char> commitment_value(bucket_commitment.begin(), bucket_commitment.end());
libzerocash::convertBytesVectorToVector(commitment_value, commitment_bv);
tree.insertElement(commitment_bv, index);
if (bucket_commitment == commitment) {
// We've found it! Now, we construct a witness.
res = true;
tree.prune();
commitment_index = index;
}
}
}
}
{
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
current_anchor = uint256(newrt_v);
}
// Consistency check: we should be able to find the current tree
// in our CCoins view.
libzerocash::IncrementalMerkleTree dummy_tree(INCREMENTAL_MERKLE_TREE_DEPTH);
assert(pcoinsTip->GetAnchorAt(current_anchor, dummy_tree));
pindex = chainActive.Next(pindex);
}
if (res) {
assert(tree.getWitness(commitment_index, path));
}
path_index = libzerocash::convertVectorToInt(commitment_index);
final_anchor = current_anchor;
return res;
}
/**
* Scan the block chain (starting in pindexStart) for transactions
* from or to us. If fUpdate is true, found transactions that already

1
src/wallet/wallet.h

@ -616,6 +616,7 @@ public:
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate);
void EraseFromWallet(const uint256 &hash);
bool WitnessBucketCommitment(uint256 &commitment, libsnark::merkle_authentication_path& path, size_t &path_index, uint256 &final_anchor);
int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime);

Loading…
Cancel
Save