github.com/vipernet-xyz/tendermint-core@v0.32.0/PHILOSOPHY.md (about)

     1  ## Design goals
     2  
     3  The design goals for Tendermint (and the SDK and related libraries) are:
     4  
     5   * Simplicity and Legibility
     6   * Parallel performance, namely ability to utilize multicore architecture
     7   * Ability to evolve the codebase bug-free
     8   * Debuggability
     9   * Complete correctness that considers all edge cases, esp in concurrency
    10   * Future-proof modular architecture, message protocol, APIs, and encapsulation
    11  
    12  
    13  ### Justification
    14  
    15  Legibility is key to maintaining bug-free software as it evolves toward more
    16  optimizations, more ease of debugging, and additional features.
    17  
    18  It is too easy to introduce bugs over time by replacing lines of code with
    19  those that may panic, which means ideally locks are unlocked by defer
    20  statements.
    21  
    22  For example,
    23  
    24  ```go
    25  func (obj *MyObj) something() {
    26  	mtx.Lock()
    27  	obj.something = other
    28  	mtx.Unlock()
    29  }
    30  ```
    31  
    32  It is too easy to refactor the codebase in the future to replace `other` with
    33  `other.String()` for example, and this may introduce a bug that causes a
    34  deadlock.  So as much as reasonably possible, we need to be using defer
    35  statements, even though it introduces additional overhead.
    36  
    37  If it is necessary to optimize the unlocking of mutex locks, the solution is
    38  more modularity via smaller functions, so that defer'd unlocks are scoped
    39  within a smaller function.
    40  
    41  Similarly, idiomatic for-loops should always be preferred over those that use
    42  custom counters, because it is too easy to evolve the body of a for-loop to
    43  become more complicated over time, and it becomes more and more difficult to
    44  assess the correctness of such a for-loop by visual inspection.
    45  
    46  
    47  ### On performance
    48  
    49  It doesn't matter whether there are alternative implementations that are 2x or
    50  3x more performant, when the software doesn't work, deadlocks, or if bugs
    51  cannot be debugged.  By taking advantage of multicore concurrency, the
    52  Tendermint implementation will at least be an order of magnitude within the
    53  range of what is theoretically possible.  The design philosophy of Tendermint,
    54  and the choice of Go as implementation language, is designed to make Tendermint
    55  implementation the standard specification for concurrent BFT software.
    56  
    57  By focusing on the message protocols (e.g. ABCI, p2p messages), and
    58  encapsulation e.g. IAVL module, (relatively) independent reactors, we are both
    59  implementing a standard implementation to be used as the specification for
    60  future implementations in more optimizable languages like Rust, Java, and C++;
    61  as well as creating sufficiently performant software. Tendermint Core will
    62  never be as fast as future implementations of the Tendermint Spec, because Go
    63  isn't designed to be as fast as possible.  The advantage of using Go is that we
    64  can develop the whole stack of modular components **faster** than in other
    65  languages.
    66  
    67  Furthermore, the real bottleneck is in the application layer, and it isn't
    68  necessary to support more than a sufficiently decentralized set of validators
    69  (e.g. 100 ~ 300 validators is sufficient, with delegated bonded PoS).
    70  
    71  Instead of optimizing Tendermint performance down to the metal, lets focus on
    72  optimizing on other matters, namely ability to push feature complete software
    73  that works well enough, can be debugged and maintained, and can serve as a spec
    74  for future implementations.
    75  
    76  
    77  ### On encapsulation
    78  
    79  In order to create maintainable, forward-optimizable software, it is critical
    80  to develop well-encapsulated objects that have well understood properties, and
    81  to re-use these easy-to-use-correctly components as building blocks for further
    82  encapsulated meta-objects.
    83  
    84  For example, mutexes are cheap enough for Tendermint's design goals when there
    85  isn't goroutine contention, so it is encouraged to create concurrency safe
    86  structures with struct-level mutexes.  If they are used in the context of
    87  non-concurrent logic, then the performance is good enough.  If they are used in
    88  the context of concurrent logic, then it will still perform correctly.
    89  
    90  Examples of this design principle can be seen in the types.ValidatorSet struct,
    91  and the rand.Rand struct.  It's one single struct declaration that can be used
    92  in both concurrent and non-concurrent logic, and due to its well encapsulation,
    93  it's easy to get the usage of the mutex right.
    94  
    95  #### example: rand.Rand:
    96  
    97  `The default Source is safe for concurrent use by multiple goroutines, but
    98  Sources created by NewSource are not`.  The reason why the default
    99  package-level source is safe for concurrent use is because it is protected (see
   100  `lockedSource` in https://golang.org/src/math/rand/rand.go).
   101  
   102  But we shouldn't rely on the global source, we should be creating our own
   103  Rand/Source instances and using them, especially for determinism in testing.
   104  So it is reasonable to have rand.Rand be protected by a mutex.  Whether we want
   105  our own implementation of Rand is another question, but the answer there is
   106  also in the affirmative.  Sometimes you want to know where Rand is being used
   107  in your code, so it becomes a simple matter of dropping in a log statement to
   108  inject inspectability into Rand usage.  Also, it is nice to be able to extend
   109  the functionality of Rand with custom methods.  For these reasons, and for the
   110  reasons which is outlined in this design philosophy document, we should
   111  continue to use the rand.Rand object, with mutex protection.
   112  
   113  Another key aspect of good encapsulation is the choice of exposed vs unexposed
   114  methods.  It should be clear to the reader of the code, which methods are
   115  intended to be used in what context, and what safe usage is.  Part of this is
   116  solved by hiding methods via unexported methods.  Another part of this is
   117  naming conventions on the methods (e.g. underscores) with good documentation,
   118  and code organization.  If there are too many exposed methods and it isn't
   119  clear what methods have what side effects, then there is something wrong about
   120  the design of abstractions that should be revisited.
   121  
   122  
   123  ### On concurrency
   124  
   125  In order for Tendermint to remain relevant in the years to come, it is vital
   126  for Tendermint to take advantage of multicore architectures.  Due to the nature
   127  of the problem, namely consensus across a concurrent p2p gossip network, and to
   128  handle RPC requests for a large number of consuming subscribers, it is
   129  unavoidable for Tendermint development to require expertise in concurrency
   130  design, especially when it comes to the reactor design, and also for RPC
   131  request handling.
   132  
   133  
   134  ## Guidelines
   135  
   136  Here are some guidelines for designing for (sufficient) performance and concurrency:
   137  
   138   * Mutex locks are cheap enough when there isn't contention.
   139   * Do not optimize code without analytical or observed proof that it is in a hot path.
   140   * Don't over-use channels when mutex locks w/ encapsulation are sufficient.
   141   * The need to drain channels are often a hint of unconsidered edge cases.
   142   * The creation of O(N) one-off goroutines is generally technical debt that
   143     needs to get addressed sooner than later.  Avoid creating too many
   144  goroutines as a patch around incomplete concurrency design, or at least be
   145  aware of the debt and do not invest in the debt.  On the other hand, Tendermint
   146  is designed to have a limited number of peers (e.g. 10 or 20), so the creation
   147  of O(C) goroutines per O(P) peers is still O(C\*P=constant).
   148    * Use defer statements to unlock as much as possible.  If you want to unlock sooner,
   149      try to create more modular functions that do make use of defer statements.
   150  
   151  ## Matras
   152  
   153  * Premature optimization kills
   154  * Readability is paramount
   155  * Beautiful is better than fast.
   156  * In the face of ambiguity, refuse the temptation to guess.
   157  * In the face of bugs, refuse the temptation to cover the bug.
   158  * There should be one-- and preferably only one --obvious way to do it.