valid_proof() && resolve_conflicts() may be has risk
Splendid work!After review the blockchain.py, I suppose If a node has the chain(may be just a fake chain) which can pass the valid_proof() function and length is bigger than all the others node’s chain, when resolve_conflicts() run, all the nodes will be synchronized as the fake chain.
@greatcz it would just mean that the "fake" chain is the right one as long as it is the longest one and can pass all the valid_proof checks. All the other nodes should be synchronized with it — that's the idea behind the consensus in Blockchain systems. But remember that this implementation is quite simple to demonstrate basic principles, so in a real systems this algorithm is slightly more complicated.
Of course, the implementation of the Proof of Work at the Learn Blockchains by Building One article is not the best in case of sustaining the required security, as Konstantin Schubert mentioned in the comments. But this repository has the required fix, so you won't be able to tamper a longer chain with precalculated proofs which are not bound to the blockchain. This line of the valid_proof method adds the hash of the previous block to tie up the current chain with proofs:
guess = f'{last_proof}{proof}{last_hash}'.encode()
You may be missing the fact that calculation of the proof (mining) should theoretically be a hard task to perform, so it should be really hard to build a longer valid chain from scratch. In a real Blockchain systems like Bitcoin the algorithm will adjust its difficulty depending on the calculation power of all the nodes, as mentioned at this Proof of work wiki article.
Also, check out What does the term “Longest chain” mean? StackExchange question, it should clarify everything for you.
@d2718nis thanks so much for the patient reply! I appreciate all that input. Lots of info I didn't know! Thanks:)