Alright, sadly, I think I've been overthinking it way too hard - sadly because I was quite enjoying trying to piece it together.
So the probability of finding a successful hash is equal to the target hash value over the total hash values. The target hash value is calculated directly from the difficulty I believe.
So say p = probability of finding a hash, tg = target hash value (calculated from difficulty somehow - this would be in the code somewhere), and tot = total hashes possible (2^256 for a 256 bit hash, etc...)
So:
p = tg / tot
But also, due to the (Strong or Weak) Law of Large Numbers (
http://en.wikipedia.org/wiki/Law_of_large_numbers), we can say that the relative frequency of event occurances approaches the probability of event occurances when you have lots of events (hashes).
I would say that we are dealing with pretty big numbers (though not really very large when compared to the total address space, otherwise reversing a hash would be too easy), so we can probably safely operate under this assumption.
So, we can say f = frequency of successful hashes, all = count of all hashes in a time period, and good = count of successful hashes in the same time period
// Common sense
f = good / all
// Apply law of large numbers
p = f
tg / tot = good / all
// The only unknown is all
all = good * tot / tg
So, to calculate the average hashrate for a given period, simply:
1) Take the total number of blocks generated for that period
2) Muliply it by the total number of hash possibilities (2^256 for a 256 bit hash)
3) Divide it by the number of hashes that would be considered "valid" for a block (the target hash value)
The number of valid hashes can be calculated from the difficulty, which is something that is coded into the protocol.
A good place to start to find out how difficulty is transformed into a target hash value would be:
https://github.com/bitcoin/bitcoin/blob/ca1913e8f64fc245157b008d6b37160306aa1d83/src/miner.cppJust search for "target".