Compare commits

...

10 Commits

Author SHA1 Message Date
Ethan Buchman
1e1ca15bcc Merge pull request #3062 from tendermint/release/v0.27.4
Release/v0.27.4
2018-12-21 16:35:45 -05:00
Ethan Buchman
c6604b5a9b changelog and version 2018-12-21 16:31:28 -05:00
Anton Kaliaev
c510f823e7 mempool: move tx to back, not front (#3036)
because we pop txs from the front if the cache is full

Refs #3035
2018-12-21 16:28:21 -05:00
Ethan Buchman
0138530df2 Merge pull request #3032 from tendermint/release/v0.27.3
Release/v0.27.3
2018-12-16 14:30:23 -05:00
Ethan Buchman
0533c73a50 crypto: revert to mainline Go crypto lib (#3027)
* crypto: revert to mainline Go crypto lib

We used to use a fork for a modified bcrypt so we could pass our own
randomness but this was largely unecessary, unused, and a burden.
So now we just use the mainline Go crypto lib.

* changelog

* fix tests

* version and changelog
2018-12-16 14:19:38 -05:00
Ethan Buchman
1beb45511c Merge pull request #3031 from tendermint/master
Merge pull request #3030 from tendermint/release/v0.27.2
2018-12-16 14:13:57 -05:00
Ethan Buchman
4a568fcedb Merge pull request #3030 from tendermint/release/v0.27.2
Release/v0.27.2
2018-12-16 14:13:30 -05:00
Ethan Buchman
b3141d7d02 makeNodeInfo returns error (#3029)
* makeNodeInfo returns error

* version and changelog
2018-12-16 14:05:58 -05:00
Jae Kwon
9a6dd96cba Revert to using defers in addrbook. (#3025)
* Revert to using defers in addrbook.  ValidateBasic->Validate since it requires DNS

* Update CHANGELOG_PENDING
2018-12-16 12:27:16 -05:00
Ethan Buchman
9fa959619a Merge pull request #3026 from tendermint/master
Merge pull request #3023 from tendermint/release/v0.27.1
2018-12-16 12:25:19 -05:00
21 changed files with 102 additions and 54 deletions

View File

@@ -1,5 +1,38 @@
# Changelog # Changelog
## v0.27.4
*December 21st, 2018*
### BUG FIXES:
- [mempool] [\#3036](https://github.com/tendermint/tendermint/issues/3036) Fix
LRU cache by popping the least recently used item when the cache is full,
not the most recently used one!
## v0.27.3
*December 16th, 2018*
### BREAKING CHANGES:
* Go API
- [dep] [\#3027](https://github.com/tendermint/tendermint/issues/3027) Revert to mainline Go crypto library, eliminating the modified
`bcrypt.GenerateFromPassword`
## v0.27.2
*December 16th, 2018*
### IMPROVEMENTS:
- [node] [\#3025](https://github.com/tendermint/tendermint/issues/3025) Validate NodeInfo addresses on startup.
### BUG FIXES:
- [p2p] [\#3025](https://github.com/tendermint/tendermint/pull/3025) Revert to using defers in addrbook. Fixes deadlocks in pex and consensus upon invalid ExternalAddr/ListenAddr configuration.
## v0.27.1 ## v0.27.1
*December 15th, 2018* *December 15th, 2018*
@@ -72,17 +105,17 @@ message.
### IMPROVEMENTS: ### IMPROVEMENTS:
- [state] [\#2929](https://github.com/tendermint/tendermint/issues/2929) Minor refactor of updateState logic (@danil-lashin) - [state] [\#2929](https://github.com/tendermint/tendermint/issues/2929) Minor refactor of updateState logic (@danil-lashin)
- [node] \#2959 Allow node to start even if software's BlockProtocol is - [node] [\#2959](https://github.com/tendermint/tendermint/issues/2959) Allow node to start even if software's BlockProtocol is
different from state's BlockProtocol different from state's BlockProtocol
- [pex] \#2959 Pex reactor logger uses `module=pex` - [pex] [\#2959](https://github.com/tendermint/tendermint/issues/2959) Pex reactor logger uses `module=pex`
### BUG FIXES: ### BUG FIXES:
- [p2p] \#2968 Panic on transport error rather than continuing to run but not - [p2p] [\#2968](https://github.com/tendermint/tendermint/issues/2968) Panic on transport error rather than continuing to run but not
accept new connections accept new connections
- [p2p] \#2969 Fix mismatch in peer count between `/net_info` and the prometheus - [p2p] [\#2969](https://github.com/tendermint/tendermint/issues/2969) Fix mismatch in peer count between `/net_info` and the prometheus
metrics metrics
- [rpc] \#2408 `/broadcast_tx_commit`: Fix "interface conversion: interface {} in nil, not EventDataTx" panic (could happen if somebody sent a tx using `/broadcast_tx_commit` while Tendermint was being stopped) - [rpc] [\#2408](https://github.com/tendermint/tendermint/issues/2408) `/broadcast_tx_commit`: Fix "interface conversion: interface {} in nil, not EventDataTx" panic (could happen if somebody sent a tx using `/broadcast_tx_commit` while Tendermint was being stopped)
- [state] [\#2785](https://github.com/tendermint/tendermint/issues/2785) Fix accum for new validators to be `-1.125*totalVotingPower` - [state] [\#2785](https://github.com/tendermint/tendermint/issues/2785) Fix accum for new validators to be `-1.125*totalVotingPower`
instead of 0, forcing them to wait before becoming the proposer. Also: instead of 0, forcing them to wait before becoming the proposer. Also:
- do not batch clip - do not batch clip

View File

@@ -1,4 +1,4 @@
## v0.27.2 ## v0.27.4
*TBD* *TBD*
@@ -21,3 +21,4 @@ Special thanks to external contributors on this release:
### IMPROVEMENTS: ### IMPROVEMENTS:
### BUG FIXES: ### BUG FIXES:

5
Gopkg.lock generated
View File

@@ -376,7 +376,7 @@
version = "v0.14.1" version = "v0.14.1"
[[projects]] [[projects]]
digest = "1:72b71e3a29775e5752ed7a8012052a3dee165e27ec18cedddae5288058f09acf" digest = "1:00d2b3e64cdc3fa69aa250dfbe4cc38c4837d4f37e62279be2ae52107ffbbb44"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
packages = [ packages = [
"bcrypt", "bcrypt",
@@ -397,8 +397,7 @@
"salsa20/salsa", "salsa20/salsa",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447"
source = "github.com/tendermint/crypto"
[[projects]] [[projects]]
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"

View File

@@ -81,8 +81,7 @@
[[constraint]] [[constraint]]
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
source = "github.com/tendermint/crypto" revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447"
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
[[override]] [[override]]
name = "github.com/jmhodges/levigo" name = "github.com/jmhodges/levigo"

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"golang.org/x/crypto/openpgp/armor" // forked to github.com/tendermint/crypto "golang.org/x/crypto/openpgp/armor"
) )
func EncodeArmor(blockType string, headers map[string]string, data []byte) string { func EncodeArmor(blockType string, headers map[string]string, data []byte) string {

View File

@@ -7,7 +7,7 @@ import (
"io" "io"
amino "github.com/tendermint/go-amino" amino "github.com/tendermint/go-amino"
"golang.org/x/crypto/ed25519" // forked to github.com/tendermint/crypto "golang.org/x/crypto/ed25519"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/crypto/tmhash"

View File

@@ -3,7 +3,7 @@ package crypto
import ( import (
"crypto/sha256" "crypto/sha256"
"golang.org/x/crypto/ripemd160" // forked to github.com/tendermint/crypto "golang.org/x/crypto/ripemd160"
) )
func Sha256(bytes []byte) []byte { func Sha256(bytes []byte) []byte {

View File

@@ -9,7 +9,7 @@ import (
secp256k1 "github.com/tendermint/btcd/btcec" secp256k1 "github.com/tendermint/btcd/btcec"
amino "github.com/tendermint/go-amino" amino "github.com/tendermint/go-amino"
"golang.org/x/crypto/ripemd160" // forked to github.com/tendermint/crypto "golang.org/x/crypto/ripemd160"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
) )

View File

@@ -8,7 +8,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"golang.org/x/crypto/chacha20poly1305" // forked to github.com/tendermint/crypto "golang.org/x/crypto/chacha20poly1305"
) )
// Implements crypto.AEAD // Implements crypto.AEAD

View File

@@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"golang.org/x/crypto/nacl/secretbox" // forked to github.com/tendermint/crypto "golang.org/x/crypto/nacl/secretbox"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"

View File

@@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt" // forked to github.com/tendermint/crypto "golang.org/x/crypto/bcrypt"
"github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto"
) )
@@ -30,9 +30,7 @@ func TestSimpleWithKDF(t *testing.T) {
plaintext := []byte("sometext") plaintext := []byte("sometext")
secretPass := []byte("somesecret") secretPass := []byte("somesecret")
salt := []byte("somesaltsomesalt") // len 16 secret, err := bcrypt.GenerateFromPassword(secretPass, 12)
// NOTE: we use a fork of x/crypto so we can inject our own randomness for salt
secret, err := bcrypt.GenerateFromPassword(salt, secretPass, 12)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@@ -676,7 +676,7 @@ func (cache *mapTxCache) Push(tx types.Tx) bool {
// Use the tx hash in the cache // Use the tx hash in the cache
txHash := sha256.Sum256(tx) txHash := sha256.Sum256(tx)
if moved, exists := cache.map_[txHash]; exists { if moved, exists := cache.map_[txHash]; exists {
cache.list.MoveToFront(moved) cache.list.MoveToBack(moved)
return false return false
} }

View File

@@ -348,20 +348,21 @@ func NewNode(config *cfg.Config,
indexerService := txindex.NewIndexerService(txIndexer, eventBus) indexerService := txindex.NewIndexerService(txIndexer, eventBus)
indexerService.SetLogger(logger.With("module", "txindex")) indexerService.SetLogger(logger.With("module", "txindex"))
var ( p2pLogger := logger.With("module", "p2p")
p2pLogger = logger.With("module", "p2p") nodeInfo, err := makeNodeInfo(
nodeInfo = makeNodeInfo( config,
config, nodeKey.ID(),
nodeKey.ID(), txIndexer,
txIndexer, genDoc.ChainID,
genDoc.ChainID, p2p.NewProtocolVersion(
p2p.NewProtocolVersion( version.P2PProtocol, // global
version.P2PProtocol, // global state.Version.Consensus.Block,
state.Version.Consensus.Block, state.Version.Consensus.App,
state.Version.Consensus.App, ),
),
)
) )
if err != nil {
return nil, err
}
// Setup Transport. // Setup Transport.
var ( var (
@@ -782,7 +783,7 @@ func makeNodeInfo(
txIndexer txindex.TxIndexer, txIndexer txindex.TxIndexer,
chainID string, chainID string,
protocolVersion p2p.ProtocolVersion, protocolVersion p2p.ProtocolVersion,
) p2p.NodeInfo { ) (p2p.NodeInfo, error) {
txIndexerStatus := "on" txIndexerStatus := "on"
if _, ok := txIndexer.(*null.TxIndex); ok { if _, ok := txIndexer.(*null.TxIndex); ok {
txIndexerStatus = "off" txIndexerStatus = "off"
@@ -817,7 +818,8 @@ func makeNodeInfo(
nodeInfo.ListenAddr = lAddr nodeInfo.ListenAddr = lAddr
return nodeInfo err := nodeInfo.Validate()
return nodeInfo, err
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@@ -10,7 +10,6 @@ import (
"net" "net"
"time" "time"
// forked to github.com/tendermint/crypto
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
"golang.org/x/crypto/nacl/box" "golang.org/x/crypto/nacl/box"

View File

@@ -175,6 +175,9 @@ func (na *NetAddress) Same(other interface{}) bool {
// String representation: <ID>@<IP>:<PORT> // String representation: <ID>@<IP>:<PORT>
func (na *NetAddress) String() string { func (na *NetAddress) String() string {
if na == nil {
return "<nil-NetAddress>"
}
if na.str == "" { if na.str == "" {
addrStr := na.DialString() addrStr := na.DialString()
if na.ID != "" { if na.ID != "" {
@@ -186,6 +189,9 @@ func (na *NetAddress) String() string {
} }
func (na *NetAddress) DialString() string { func (na *NetAddress) DialString() string {
if na == nil {
return "<nil-NetAddress>"
}
return net.JoinHostPort( return net.JoinHostPort(
na.IP.String(), na.IP.String(),
strconv.FormatUint(uint64(na.Port), 10), strconv.FormatUint(uint64(na.Port), 10),

View File

@@ -36,7 +36,7 @@ type nodeInfoAddress interface {
// nodeInfoTransport validates a nodeInfo and checks // nodeInfoTransport validates a nodeInfo and checks
// our compatibility with it. It's for use in the handshake. // our compatibility with it. It's for use in the handshake.
type nodeInfoTransport interface { type nodeInfoTransport interface {
ValidateBasic() error Validate() error
CompatibleWith(other NodeInfo) error CompatibleWith(other NodeInfo) error
} }
@@ -103,7 +103,7 @@ func (info DefaultNodeInfo) ID() ID {
return info.ID_ return info.ID_
} }
// ValidateBasic checks the self-reported DefaultNodeInfo is safe. // Validate checks the self-reported DefaultNodeInfo is safe.
// It returns an error if there // It returns an error if there
// are too many Channels, if there are any duplicate Channels, // are too many Channels, if there are any duplicate Channels,
// if the ListenAddr is malformed, or if the ListenAddr is a host name // if the ListenAddr is malformed, or if the ListenAddr is a host name
@@ -116,7 +116,7 @@ func (info DefaultNodeInfo) ID() ID {
// International clients could then use punycode (or we could use // International clients could then use punycode (or we could use
// url-encoding), and we just need to be careful with how we handle that in our // url-encoding), and we just need to be careful with how we handle that in our
// clients. (e.g. off by default). // clients. (e.g. off by default).
func (info DefaultNodeInfo) ValidateBasic() error { func (info DefaultNodeInfo) Validate() error {
// ID is already validated. // ID is already validated.

View File

@@ -12,7 +12,7 @@ func TestNodeInfoValidate(t *testing.T) {
// empty fails // empty fails
ni := DefaultNodeInfo{} ni := DefaultNodeInfo{}
assert.Error(t, ni.ValidateBasic()) assert.Error(t, ni.Validate())
channels := make([]byte, maxNumChannels) channels := make([]byte, maxNumChannels)
for i := 0; i < maxNumChannels; i++ { for i := 0; i < maxNumChannels; i++ {
@@ -68,13 +68,13 @@ func TestNodeInfoValidate(t *testing.T) {
// test case passes // test case passes
ni = testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo) ni = testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo)
ni.Channels = channels ni.Channels = channels
assert.NoError(t, ni.ValidateBasic()) assert.NoError(t, ni.Validate())
for _, tc := range testCases { for _, tc := range testCases {
ni := testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo) ni := testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo)
ni.Channels = channels ni.Channels = channels
tc.malleateNodeInfo(&ni) tc.malleateNodeInfo(&ni)
err := ni.ValidateBasic() err := ni.Validate()
if tc.expectErr { if tc.expectErr {
assert.Error(t, err, tc.testName) assert.Error(t, err, tc.testName)
} else { } else {

View File

@@ -162,26 +162,29 @@ func (a *addrBook) FilePath() string {
// AddOurAddress one of our addresses. // AddOurAddress one of our addresses.
func (a *addrBook) AddOurAddress(addr *p2p.NetAddress) { func (a *addrBook) AddOurAddress(addr *p2p.NetAddress) {
a.Logger.Info("Add our address to book", "addr", addr)
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock()
a.Logger.Info("Add our address to book", "addr", addr)
a.ourAddrs[addr.String()] = struct{}{} a.ourAddrs[addr.String()] = struct{}{}
a.mtx.Unlock()
} }
// OurAddress returns true if it is our address. // OurAddress returns true if it is our address.
func (a *addrBook) OurAddress(addr *p2p.NetAddress) bool { func (a *addrBook) OurAddress(addr *p2p.NetAddress) bool {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock()
_, ok := a.ourAddrs[addr.String()] _, ok := a.ourAddrs[addr.String()]
a.mtx.Unlock()
return ok return ok
} }
func (a *addrBook) AddPrivateIDs(IDs []string) { func (a *addrBook) AddPrivateIDs(IDs []string) {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock()
for _, id := range IDs { for _, id := range IDs {
a.privateIDs[p2p.ID(id)] = struct{}{} a.privateIDs[p2p.ID(id)] = struct{}{}
} }
a.mtx.Unlock()
} }
// AddAddress implements AddrBook // AddAddress implements AddrBook
@@ -191,6 +194,7 @@ func (a *addrBook) AddPrivateIDs(IDs []string) {
func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error { func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock() defer a.mtx.Unlock()
return a.addAddress(addr, src) return a.addAddress(addr, src)
} }
@@ -198,6 +202,7 @@ func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error {
func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) { func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock() defer a.mtx.Unlock()
ka := a.addrLookup[addr.ID] ka := a.addrLookup[addr.ID]
if ka == nil { if ka == nil {
return return
@@ -211,14 +216,16 @@ func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) {
func (a *addrBook) IsGood(addr *p2p.NetAddress) bool { func (a *addrBook) IsGood(addr *p2p.NetAddress) bool {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock() defer a.mtx.Unlock()
return a.addrLookup[addr.ID].isOld() return a.addrLookup[addr.ID].isOld()
} }
// HasAddress returns true if the address is in the book. // HasAddress returns true if the address is in the book.
func (a *addrBook) HasAddress(addr *p2p.NetAddress) bool { func (a *addrBook) HasAddress(addr *p2p.NetAddress) bool {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock()
ka := a.addrLookup[addr.ID] ka := a.addrLookup[addr.ID]
a.mtx.Unlock()
return ka != nil return ka != nil
} }
@@ -292,6 +299,7 @@ func (a *addrBook) PickAddress(biasTowardsNewAddrs int) *p2p.NetAddress {
func (a *addrBook) MarkGood(addr *p2p.NetAddress) { func (a *addrBook) MarkGood(addr *p2p.NetAddress) {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock() defer a.mtx.Unlock()
ka := a.addrLookup[addr.ID] ka := a.addrLookup[addr.ID]
if ka == nil { if ka == nil {
return return
@@ -306,6 +314,7 @@ func (a *addrBook) MarkGood(addr *p2p.NetAddress) {
func (a *addrBook) MarkAttempt(addr *p2p.NetAddress) { func (a *addrBook) MarkAttempt(addr *p2p.NetAddress) {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock() defer a.mtx.Unlock()
ka := a.addrLookup[addr.ID] ka := a.addrLookup[addr.ID]
if ka == nil { if ka == nil {
return return
@@ -461,12 +470,13 @@ ADDRS_LOOP:
// ListOfKnownAddresses returns the new and old addresses. // ListOfKnownAddresses returns the new and old addresses.
func (a *addrBook) ListOfKnownAddresses() []*knownAddress { func (a *addrBook) ListOfKnownAddresses() []*knownAddress {
addrs := []*knownAddress{}
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock()
addrs := []*knownAddress{}
for _, addr := range a.addrLookup { for _, addr := range a.addrLookup {
addrs = append(addrs, addr.copy()) addrs = append(addrs, addr.copy())
} }
a.mtx.Unlock()
return addrs return addrs
} }
@@ -476,6 +486,7 @@ func (a *addrBook) ListOfKnownAddresses() []*knownAddress {
func (a *addrBook) Size() int { func (a *addrBook) Size() int {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock() defer a.mtx.Unlock()
return a.size() return a.size()
} }

View File

@@ -24,7 +24,7 @@ type mockNodeInfo struct {
func (ni mockNodeInfo) ID() ID { return ni.addr.ID } func (ni mockNodeInfo) ID() ID { return ni.addr.ID }
func (ni mockNodeInfo) NetAddress() *NetAddress { return ni.addr } func (ni mockNodeInfo) NetAddress() *NetAddress { return ni.addr }
func (ni mockNodeInfo) ValidateBasic() error { return nil } func (ni mockNodeInfo) Validate() error { return nil }
func (ni mockNodeInfo) CompatibleWith(other NodeInfo) error { return nil } func (ni mockNodeInfo) CompatibleWith(other NodeInfo) error { return nil }
func AddPeerToSwitch(sw *Switch, peer Peer) { func AddPeerToSwitch(sw *Switch, peer Peer) {

View File

@@ -350,7 +350,7 @@ func (mt *MultiplexTransport) upgrade(
} }
} }
if err := nodeInfo.ValidateBasic(); err != nil { if err := nodeInfo.Validate(); err != nil {
return nil, nil, ErrRejected{ return nil, nil, ErrRejected{
conn: c, conn: c,
err: err, err: err,

View File

@@ -18,7 +18,7 @@ const (
// TMCoreSemVer is the current version of Tendermint Core. // TMCoreSemVer is the current version of Tendermint Core.
// It's the Semantic Version of the software. // It's the Semantic Version of the software.
// Must be a string because scripts like dist.sh read this file. // Must be a string because scripts like dist.sh read this file.
TMCoreSemVer = "0.27.1" TMCoreSemVer = "0.27.4"
// ABCISemVer is the semantic version of the ABCI library // ABCISemVer is the semantic version of the ABCI library
ABCISemVer = "0.15.0" ABCISemVer = "0.15.0"