Permalink
Browse files

Prevent multiple submissions of same work in merged mining

  • Loading branch information...
1 parent ffb5ff5 commit 2e0e926b1d8abfbda109c5ba4a79979e306e0fe3 @vinced vinced committed Jul 17, 2011
Showing with 144 additions and 54 deletions.
  1. +30 −17 contrib/merged-mine-proxy
  2. +13 −3 doc/README_merged-mining.md
  3. +44 −8 src/auxpow.cpp
  4. +1 −3 src/auxpow.h
  5. +44 −8 src/main.cpp
  6. +11 −13 src/main.h
  7. +1 −2 src/rpc.cpp
@@ -16,6 +16,7 @@ from datetime import datetime
from twisted.internet import defer, reactor
from twisted.web import server, resource, client
from twisted.internet.error import ConnectionRefusedError
+import twisted.internet.error
from urlparse import urlsplit
__version__ = '0.1'
@@ -40,7 +41,7 @@ Currently only one aux chain is supported.
'''
class Error(Exception):
- def __init__(self, code, message, data=None):
+ def __init__(self, code, message, data=''):
if not isinstance(code, int):
raise TypeError('code must be an int')
if not isinstance(message, unicode):
@@ -74,26 +75,33 @@ class Proxy(object):
}
if self._auth is not None:
headers['Authorization'] = 'Basic ' + base64.b64encode(self._auth)
- resp = json.loads((yield client.getPage(
- url=self._url,
- method='POST',
- headers=headers,
- postdata=json.dumps({
- 'jsonrpc': '2.0',
- 'method': method,
- 'params': params,
- 'id': id_,
- }),
- )))
+ resp = None
+ try:
+ resp = (yield client.getPage(
+ url=self._url,
+ method='POST',
+ headers=headers,
+ postdata=json.dumps({
+ 'jsonrpc': '2.0',
+ 'method': method,
+ 'params': params,
+ 'id': id_,
+ }),
+ ))
+ except twisted.web.error.Error, e:
+ resp = e.response
+
+ resp = json.loads(resp)
if resp['id'] != id_:
raise ValueError('invalid id')
if 'error' in resp and resp['error'] is not None:
- raise Error(resp['error'])
+ print>>sys.stderr, "Error from %s : %s" %(self._url, resp['error'])
+ raise Error(resp['error']['code'], resp['error']['message'])
defer.returnValue(resp['result'])
except ConnectionRefusedError:
print>>sys.stderr, "Could not connect to %s" %(self._url)
- raise
+ raise Error(-32099, u'Could not connect to backend', self._netloc)
def __getattr__(self, attr):
if attr.startswith('rpc_'):
@@ -213,8 +221,11 @@ class Listener(Server):
print>>sys.stderr, "work submitted, but parent rejected"
defer.returnValue(False)
else:
+ aux = proof['aux']
+ # strip off merkle tree size and nonce
+ aux_hash = proof['aux'][0:-16]
aux_solved = (
- yield self.auxs[0].rpc_getauxblock(proof['aux'], proof['auxpow']))
+ yield self.auxs[0].rpc_getauxblock(aux_hash, proof['auxpow']))
parent_solved = (
yield self.parent.rpc_getworkaux("", data))
print "%s,solve,%s,%s" %(datetime.utcnow().isoformat(),
@@ -227,15 +238,17 @@ class Listener(Server):
aux_block_hash = aux_block['hash']
# trivial merkle tree for now
aux_merkle_root = aux_block_hash
- work = (yield self.parent.rpc_getworkaux(aux_merkle_root))
+ # merkle tree size = 1, nonce = 0
+ aux = aux_merkle_root + "01000000" + "00000000"
+ work = (yield self.parent.rpc_getworkaux(aux))
# Find highest target
targets = [work['target'][::-1]] # reverse hex string due to endianess
targets.append(aux_block['target'][::-1])
targets.sort()
work['target'] = targets[-1][::-1]
defer.returnValue(work)
except Exception:
- traceback.print_exc()
+ #traceback.print_exc()
raise
def main(args):
@@ -21,6 +21,7 @@ The validation proceeds as follows:
* verify that the merkle root is in the parent coinbase
* verify that the parent coinbase tx is properly attached to the merkle tree of the parent block
* verify that the parent block hash is under the aux target
+* verify that the aux block is at a fixed slot in the chain merkle tree
Mining
======
@@ -38,7 +39,16 @@ Currently acceptance of aux proof of work is disabled on the production bitcoin
To enable a synchronized upgrade to a chain, a starting block number can be configured in GetAuxPowStartBlock().
-Issues
-======
+How To Example for Bitcoin / Namecoin
+=====================================
+
+This example assumes bitcoin RPC port is 8332 and namecoin is at port 8331.
+
+Compile namecoind and bitcoind with the merged mining patch. Then run:
+
+ `contrib/merged-mine-proxy -w 8330 -p http://pw:un@127.0.0.1:8332/ -x http://pw:un@127.0.0.1:8331/`
+
+This will have the proxy listen at port 8330.
+
+Point your miner at 127.0.0.1 port 8330 and start mining.
-* The code as it stands allows multiple aux blocks for the same aux chain to be generated at once if the blocks are put at different positions in the merkle tree. Unless another solution is found, each aux chain must only accept blocks at a fixed position in the chain list.
View
@@ -9,23 +9,59 @@
using namespace std;
using namespace boost;
-bool CAuxPow::Check(uint256 hashAuxBlock)
+bool CAuxPow::Check(uint256 hashAuxBlock, int nChainID)
{
+ if (parentBlock.GetChainID() == nChainID)
+ return error("Aux POW parent has our chain ID");
+
+ if (vChainMerkleBranch.size() > 30)
+ return error("Aux POW chain merkle branch too long");
+
// Check that the chain merkle root is in the coinbase
uint256 nRootHash = CBlock::CheckMerkleBranch(hashAuxBlock, vChainMerkleBranch, nChainIndex);
vector<unsigned char> vchRootHash(nRootHash.begin(), nRootHash.end());
std::reverse(vchRootHash.begin(), vchRootHash.end()); // correct endian
+ // Check that we are in the parent block merkle tree
+ if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != parentBlock.hashMerkleRoot)
+ return error("Aux POW merkle root incorrect");
+
const CScript script = vin[0].scriptSig;
- if (std::search(script.begin(), script.end(), vchRootHash.begin(), vchRootHash.end()) ==
- script.end()
- )
- return false;
+ CScript::const_iterator pc =
+ std::search(script.begin(), script.end(), vchRootHash.begin(), vchRootHash.end());
- // Check that we are in the parent block merkle tree
- if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != parentBlock.hashMerkleRoot)
- return false;
+ if (pc == script.end())
+ return error("Aux POW missing chain merkle root in parent coinbase");
+
+ // Check that the same work is not submitted twice to our chain.
+ // Ensure we are at a deterministic point in the merkle leaves by hashing
+ // a nonce and our chain ID and comparing to the index.
+ pc += vchRootHash.size();
+ if (script.end() - pc < 8)
+ return error("Aux POW missing chain merkle tree size and nonce in parent coinbase");
+
+ int nSize;
+ memcpy(&nSize, &pc[0], 4);
+ if (nSize != (1 << vChainMerkleBranch.size()))
+ return error("Aux POW merkle branch size does not match parent coinbase");
+
+ int nNonce;
+ memcpy(&nNonce, &pc[4], 4);
+
+ // Choose a pseudo-random slot in the chain merkle tree
+ // but have it be fixed for a size/nonce/chain combination.
+ //
+ // This prevents the same work from being used twice for the
+ // same chain while reducing the chance that two chains clash
+ // for the same slot.
+ unsigned int rand = nNonce;
+ rand = rand * 1103515245 + 12345;
+ rand += nChainID;
+ rand = rand * 1103515245 + 12345;
+
+ if (nChainIndex != (rand % nSize))
+ return error("Aux POW wrong index");
return true;
}
View
@@ -36,16 +36,14 @@ class CAuxPow : public CMerkleTx
nSerSize += SerReadWrite(s, parentBlock, nType | SER_BLOCKHEADERONLY, nVersion, ser_action);
)
- bool Check(uint256 hashAuxBlock);
+ bool Check(uint256 hashAuxBlock, int nChainID);
uint256 GetParentBlockHash()
{
return parentBlock.GetHash();
}
};
-bool CheckWorkWithAuxPow(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey, CAuxPow& auxpow);
-
template <typename Stream>
int ReadWriteAuxPow(Stream& s, const boost::shared_ptr<CAuxPow>& auxpow, int nType, int nVersion, CSerActionGetSerializeSize ser_action)
{
View
@@ -1238,20 +1238,44 @@ int GetAuxPowStartBlock()
return INT_MAX; // Never on prodnet
}
+int GetOurChainID()
+{
+ return 0x0000;
+}
bool CBlock::CheckProofOfWork(int nHeight) const
{
-
- if (auxpow.get() != NULL && nHeight >= GetAuxPowStartBlock())
+ if (nHeight >= GetAuxPowStartBlock())
{
- if (!auxpow->Check(GetHash()))
- return error("CheckProofOfWork() : AUX POW is not valid");
- // Check proof of work matches claimed amount
- if (!::CheckProofOfWork(auxpow->GetParentBlockHash(), nBits))
- return error("CheckProofOfWork() : AUX proof of work failed");
+ // Prevent same work from being submitted twice:
+ // - this block must have our chain ID
+ // - parent block must not have the same chain ID (see CAuxPow::Check)
+ // - index of this chain in chain merkle tree must be pre-determined (see CAuxPow::Check)
+ if (!fTestNet && GetChainID() != GetOurChainID())
+ return error("CheckProofOfWork() : block does not have our chain ID");
+
+ if (auxpow.get() != NULL)
+ {
+ if (!auxpow->Check(GetHash(), GetChainID()))
+ return error("CheckProofOfWork() : AUX POW is not valid");
+ // Check proof of work matches claimed amount
+ if (!::CheckProofOfWork(auxpow->GetParentBlockHash(), nBits))
+ return error("CheckProofOfWork() : AUX proof of work failed");
+ }
+ else
+ {
+ // Check proof of work matches claimed amount
+ if (!::CheckProofOfWork(GetHash(), nBits))
+ return error("CheckProofOfWork() : proof of work failed");
+ }
}
else
{
+ if (auxpow.get() != NULL)
+ {
+ return error("CheckProofOfWork() : AUX POW is not allowed at this block");
+ }
+
// Check proof of work matches claimed amount
if (!::CheckProofOfWork(GetHash(), nBits))
return error("CheckProofOfWork() : proof of work failed");
@@ -2719,6 +2743,18 @@ class COrphan
}
};
+void CBlock::SetNull()
+{
+ nVersion = BLOCK_VERSION_DEFAULT | (GetOurChainID() * BLOCK_VERSION_CHAIN_START);
+ hashPrevBlock = 0;
+ hashMerkleRoot = 0;
+ nTime = 0;
+ nBits = 0;
+ nNonce = 0;
+ vtx.clear();
+ vMerkleTree.clear();
+ auxpow.reset();
+}
CBlock* CreateNewBlock(CReserveKey& reservekey)
{
@@ -2953,7 +2989,7 @@ bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey)
if (auxpow != NULL)
{
- if (!auxpow->Check(hash))
+ if (!auxpow->Check(hash, pblock->GetChainID()))
return error("AUX POW is not valid");
if (auxpow->GetParentBlockHash() > hashTarget)
View
@@ -774,7 +774,11 @@ enum
BLOCK_VERSION_DEFAULT = (1 << 0),
// modifiers
- BLOCK_VERSION_AUXPOW = (1 << 16),
+ BLOCK_VERSION_AUXPOW = (1 << 8),
+
+ // bits allocated for chain ID
+ BLOCK_VERSION_CHAIN_START = (1 << 16),
+ BLOCK_VERSION_CHAIN_END = (1 << 30),
};
@@ -834,21 +838,15 @@ class CBlock
const_cast<CBlock*>(this)->vtx.clear();
)
- void SetAuxPow(CAuxPow* pow);
-
- void SetNull()
+ int GetChainID() const
{
- nVersion = BLOCK_VERSION_DEFAULT;
- hashPrevBlock = 0;
- hashMerkleRoot = 0;
- nTime = 0;
- nBits = 0;
- nNonce = 0;
- vtx.clear();
- vMerkleTree.clear();
- auxpow.reset();
+ return nVersion / BLOCK_VERSION_CHAIN_START;
}
+ void SetAuxPow(CAuxPow* pow);
+
+ void SetNull();
+
bool IsNull() const
{
return (nBits == 0);
View
@@ -1425,7 +1425,7 @@ Value getworkaux(const Array& params, bool fHelp)
"getworkaux <aux>\n"
"getworkaux "" <data> [<chain-index> <branch>*]\n"
" get work with auxiliary data in coinbase, for multichain mining\n"
- "<aux> is the merkle root of the auxiliary chain block hashes\n"
+ "<aux> is the merkle root of the auxiliary chain block hashes concatenated with the aux chain merkle tree size and a nonce\n"
"<chain-index> is the aux chain index in the aux chain merkle tree\n"
"<branch> is the optional merkle branch of the aux chain\n"
"If <data> is not specified, returns formatted hash data to work on:\n"
@@ -1547,7 +1547,6 @@ Value getworkaux(const Array& params, bool fHelp)
if (params.size() > 2)
{
- printf("pow parent block with hash %s\n", pblock->GetHash().GetHex().c_str());
// Requested aux proof of work
int nChainIndex = params[2].get_int();
// TODO handle branch

0 comments on commit 2e0e926

Please sign in to comment.