If you knew what you know now, and you were starting a new CL from scratch, what structures would you use? - Potuz
Answers by @rauljordan
Would I still use Go if I were starting a client by myself? No – I would 100% rewrite everything in Rust for a variety of reasons:
Would I choose Go if we were to reset Prysm with the existing team? Yes – I would choose Go 100% and here's why:
There are two schools of thought that dominate client development today:
However, I believe our team culture fits more into camp (1). We have significant usage, and care about making staking rock-solid for the average user. We learn more from having constant feedback from real people than from no one using our code. Being performant at the cost of diverging from a spec is problematic because it becomes unmaintainable.
No one, but the person who wrote the feature, knows how to debug it because it reads nowhere close to what the spec says it is supposed to do. It would be much easier for knowledge silos to form.
Here's what we competed on with other teams before, when us and others were less experienced:
Here's what I think the right things to compete on are:
Why? (1) Maintainability leads to happy users, happy developers, and a happy team. It makes the code a joy to work with. As a personal finance analogy: instead of paying off our debts, we can finally start saving, and potentially reinvesting those gains.
(2) If the average user has an excellent experience, Ethereum staking grows in popularity, and therefore Ethereum becomes more secure. We learn a lot more from having people use our software, report bugs, and develop positive feedback cycles than we do from no one downloading our code. We learn from responsibility and wield it with care.
(3) There's no secret to optimizing code other than the feedback cycle of gathering data directly, seeing a large flame in the flame graph, and making it smaller. If our code is instrumentable where it matters, we can attribute bugs or regressions to specific features. We can figure out which latent variable is the real problem. Moreover, by making our code extensible, such as by making it easy to add new APIs, we encourage a lot more people to poke around and find bugs in our code!
Some duplication is better than the wrong abstraction
Don't build things you won't need
Be as abstract as possible in the internals of the codebase, be concrete at the entrypoints of the codebase
main()
is, and in the node initialization. The deeper we go, we should think in the abstractPrevent footguns by using SOLID software engineering
Do it right the first time
Make developers want to use the codebase as a dependency
go get
our code, call our endpoints with Go, interact with our database with Go, make it easy to add APIs, gather data with Prysm, build networked applications with Prysm's networking stackMake illegal states unrepresentable
Make code more futureproof
As a counter: how can we anticipate all scenarios?
If I were writing this in Rust, no need. If it were in Go, I would leverage the sync
package more, and leverage sync.Pool better or other primitives. I would have a better picture of how GC-efficient our data is, how long it must live, and of memory-locality when designing them
I would use functional code where possible. Prevent race conditions by just making things immutable, allowing for scratch pads or things that could be thrown away upon failures. I would focus on correctness first, however.
One idea is to have structs be represented as their tree-structure (serialized bytes) internally, with "views" into their fields, so that HashTreeRoot is trivial. Potuz also recommended a few approaches using red/black trees. Journal structures are also powerful in which we focus on diffs between changes rather than the full thing itself.
I would explore more approaches that use "sharding" instead of one mutex being contended among all concurrent callers/writers.
Go runtime. If in Rust, I would use a modified version of Tokio with a few abstractions over its green threads
I wouldn't be afraid to use "reflect" a little more. I would prefer we use "super-structs" more with struct tags that can tell us how to do certain things depending on the fork-kind. I would encapsulate a lot more logic as methods on our block type, to avoid needing to have conditionals and switch statements in the middle of important business logic
No. I would focus on more encapsulation, simplicity, and making code that is easier to reason about before worrying about performance. I think we have a lot more to gain of Prysm and Go regarding security/correctness before performance
I would focus on the average staker first and making the software rock-solid for them. If the builder code and docs are way better than our users' docs and they think our support for them sucks, we might need to course-correct