Skip to content

Commit 48422dd

Browse files
authored
Merge pull request #1356 from onetechnical/onetechnical/relbeta2.1.1
Onetechnical/relbeta2.1.1
2 parents c845f99 + 54a9292 commit 48422dd

File tree

20 files changed

+1217
-211
lines changed

20 files changed

+1217
-211
lines changed

agreement/abstractions.go

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package agreement
1818

1919
import (
2020
"context"
21+
"errors"
2122
"time"
2223

2324
"github.com/algorand/go-algorand/config"
@@ -65,6 +66,10 @@ type ValidatedBlock interface {
6566
Block() bookkeeping.Block
6667
}
6768

69+
// ErrAssembleBlockRoundStale is returned by AssembleBlock when the requested round number is not the
70+
// one that matches the ledger last committed round + 1.
71+
var ErrAssembleBlockRoundStale = errors.New("requested round for AssembleBlock is stale")
72+
6873
// An BlockFactory produces an Block which is suitable for proposal for a given
6974
// Round.
7075
type BlockFactory interface {

agreement/pseudonode.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,9 @@ func (n asyncPseudonode) makeProposals(round basics.Round, period period, accoun
266266
deadline := time.Now().Add(AssemblyTime)
267267
ve, err := n.factory.AssembleBlock(round, deadline)
268268
if err != nil {
269-
n.log.Errorf("pseudonode.makeProposals: could not generate a proposal for round %d: %v", round, err)
269+
if err != ErrAssembleBlockRoundStale {
270+
n.log.Errorf("pseudonode.makeProposals: could not generate a proposal for round %d: %v", round, err)
271+
}
270272
return nil, nil
271273
}
272274

buildnumber.dat

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0
1+
1

config/consensus_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package config
1818

1919
import (
2020
"testing"
21+
22+
"github.com/stretchr/testify/require"
2123
)
2224

2325
func TestConsensusParams(t *testing.T) {
@@ -34,3 +36,20 @@ func TestConsensusParams(t *testing.T) {
3436
}
3537
}
3638
}
39+
40+
// TestConsensusUpgradeWindow ensures that the upgrade window is a non-zero value, and confirm to be within the valid range.
41+
func TestConsensusUpgradeWindow(t *testing.T) {
42+
for proto, params := range Consensus {
43+
require.GreaterOrEqualf(t, params.MaxUpgradeWaitRounds, params.MinUpgradeWaitRounds, "Version :%v", proto)
44+
for toVersion, delay := range params.ApprovedUpgrades {
45+
if params.MinUpgradeWaitRounds != 0 || params.MaxUpgradeWaitRounds != 0 {
46+
require.NotZerof(t, delay, "From :%v\nTo :%v", proto, toVersion)
47+
require.GreaterOrEqualf(t, delay, params.MinUpgradeWaitRounds, "From :%v\nTo :%v", proto, toVersion)
48+
require.LessOrEqualf(t, delay, params.MaxUpgradeWaitRounds, "From :%v\nTo :%v", proto, toVersion)
49+
} else {
50+
require.Zerof(t, delay, "From :%v\nTo :%v", proto, toVersion)
51+
52+
}
53+
}
54+
}
55+
}

crypto/merkletrie/cache.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ type merkleTrieCache struct {
7272

7373
// a list of the pages priorities. The item in the front has higher priority and would not get evicted as quickly as the item on the back
7474
pagesPrioritizationList *list.List
75-
// the list element of each of the priorities
75+
// the list element of each of the priorities. The pagesPrioritizationMap maps a page id to the page priority list element.
7676
pagesPrioritizationMap map[uint64]*list.Element
7777
// the page to load before the nextNodeID at init time. If zero, then nothing is being reloaded.
7878
deferedPageLoad uint64
@@ -145,8 +145,10 @@ func (mtc *merkleTrieCache) getNode(nid storedNodeIdentifier) (pnode *node, err
145145
return
146146
}
147147

148-
// prioritizeNode make sure to move the priorities of the pages according to
149-
// the accessed node identifier
148+
// prioritizeNode make sure to adjust the priority of the given node id.
149+
// nodes are prioritized based on the page the belong to.
150+
// a new page would be placed on front, and an older page would get moved
151+
// to the front.
150152
func (mtc *merkleTrieCache) prioritizeNode(nid storedNodeIdentifier) {
151153
page := uint64(nid) / uint64(mtc.nodesPerPage)
152154

@@ -350,6 +352,9 @@ func (mtc *merkleTrieCache) commit() error {
350352
element := mtc.pagesPrioritizationMap[uint64(page)]
351353
if element != nil {
352354
mtc.pagesPrioritizationList.Remove(element)
355+
delete(mtc.pagesPrioritizationMap, uint64(page))
356+
mtc.cachedNodeCount -= len(mtc.pageToNIDsPtr[uint64(page)])
357+
delete(mtc.pageToNIDsPtr, uint64(page))
353358
}
354359
}
355360

@@ -440,6 +445,7 @@ func (mtc *merkleTrieCache) encodePage(nodeIDs map[storedNodeIdentifier]*node) [
440445
// evict releases the least used pages from cache until the number of elements in cache are less than cachedNodeCountTarget
441446
func (mtc *merkleTrieCache) evict() (removedNodes int) {
442447
removedNodes = mtc.cachedNodeCount
448+
443449
for mtc.cachedNodeCount > mtc.cachedNodeCountTarget {
444450
// get the least used page off the pagesPrioritizationList
445451
element := mtc.pagesPrioritizationList.Back()
@@ -448,6 +454,7 @@ func (mtc *merkleTrieCache) evict() (removedNodes int) {
448454
}
449455
mtc.pagesPrioritizationList.Remove(element)
450456
pageToRemove := element.Value.(uint64)
457+
delete(mtc.pagesPrioritizationMap, pageToRemove)
451458
mtc.cachedNodeCount -= len(mtc.pageToNIDsPtr[pageToRemove])
452459
delete(mtc.pageToNIDsPtr, pageToRemove)
453460
}

crypto/merkletrie/cache_test.go

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// Copyright (C) 2019-2020 Algorand, Inc.
2+
// This file is part of go-algorand
3+
//
4+
// go-algorand is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as
6+
// published by the Free Software Foundation, either version 3 of the
7+
// License, or (at your option) any later version.
8+
//
9+
// go-algorand is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package merkletrie
18+
19+
import (
20+
"fmt"
21+
"testing"
22+
23+
"github.com/stretchr/testify/require"
24+
25+
"github.com/algorand/go-algorand/crypto"
26+
)
27+
28+
func verifyCacheNodeCount(t *testing.T, trie *Trie) {
29+
count := 0
30+
for _, pageNodes := range trie.cache.pageToNIDsPtr {
31+
count += len(pageNodes)
32+
}
33+
require.Equal(t, count, trie.cache.cachedNodeCount)
34+
35+
// make sure that the pagesPrioritizationMap aligns with pagesPrioritizationList
36+
require.Equal(t, len(trie.cache.pagesPrioritizationMap), trie.cache.pagesPrioritizationList.Len())
37+
38+
// if we're not within a transaction, the following should also hold true:
39+
if !trie.cache.modified {
40+
require.Equal(t, len(trie.cache.pageToNIDsPtr), trie.cache.pagesPrioritizationList.Len())
41+
}
42+
43+
for e := trie.cache.pagesPrioritizationList.Back(); e != nil; e = e.Next() {
44+
page := e.Value.(uint64)
45+
_, has := trie.cache.pagesPrioritizationMap[page]
46+
require.True(t, has)
47+
_, has = trie.cache.pageToNIDsPtr[page]
48+
require.True(t, has)
49+
}
50+
}
51+
52+
func TestCacheEviction1(t *testing.T) {
53+
var memoryCommitter InMemoryCommitter
54+
mt1, _ := MakeTrie(&memoryCommitter, defaultTestEvictSize)
55+
// create 13000 hashes.
56+
leafsCount := 13000
57+
hashes := make([]crypto.Digest, leafsCount)
58+
for i := 0; i < len(hashes); i++ {
59+
hashes[i] = crypto.Hash([]byte{byte(i % 256), byte((i / 256) % 256), byte(i / 65536)})
60+
}
61+
62+
for i := 0; i < defaultTestEvictSize; i++ {
63+
mt1.Add(hashes[i][:])
64+
}
65+
66+
for i := defaultTestEvictSize; i < len(hashes); i++ {
67+
mt1.Add(hashes[i][:])
68+
mt1.Evict(true)
69+
require.GreaterOrEqual(t, defaultTestEvictSize, mt1.cache.cachedNodeCount)
70+
verifyCacheNodeCount(t, mt1)
71+
}
72+
}
73+
74+
func TestCacheEviction2(t *testing.T) {
75+
var memoryCommitter InMemoryCommitter
76+
mt1, _ := MakeTrie(&memoryCommitter, defaultTestEvictSize)
77+
// create 20000 hashes.
78+
leafsCount := 20000
79+
hashes := make([]crypto.Digest, leafsCount)
80+
for i := 0; i < len(hashes); i++ {
81+
hashes[i] = crypto.Hash([]byte{byte(i % 256), byte((i / 256) % 256), byte(i / 65536)})
82+
}
83+
84+
for i := 0; i < defaultTestEvictSize; i++ {
85+
mt1.Add(hashes[i][:])
86+
}
87+
88+
for i := defaultTestEvictSize; i < len(hashes); i++ {
89+
mt1.Delete(hashes[i-2][:])
90+
mt1.Add(hashes[i][:])
91+
mt1.Add(hashes[i-2][:])
92+
93+
if i%(len(hashes)/20) == 0 {
94+
mt1.Evict(true)
95+
require.GreaterOrEqual(t, defaultTestEvictSize, mt1.cache.cachedNodeCount)
96+
verifyCacheNodeCount(t, mt1)
97+
}
98+
}
99+
}
100+
101+
func TestCacheEviction3(t *testing.T) {
102+
var memoryCommitter InMemoryCommitter
103+
mt1, _ := MakeTrie(&memoryCommitter, defaultTestEvictSize)
104+
// create 200000 hashes.
105+
leafsCount := 200000
106+
hashes := make([]crypto.Digest, leafsCount)
107+
for i := 0; i < len(hashes); i++ {
108+
hashes[i] = crypto.Hash([]byte{byte(i % 256), byte((i / 256) % 256), byte(i / 65536)})
109+
}
110+
111+
for i := 0; i < defaultTestEvictSize; i++ {
112+
mt1.Add(hashes[i][:])
113+
}
114+
115+
for i := defaultTestEvictSize; i < len(hashes); i++ {
116+
mt1.Delete(hashes[i-500][:])
117+
mt1.Add(hashes[i][:])
118+
119+
if i%(len(hashes)/20) == 0 {
120+
mt1.Evict(true)
121+
require.GreaterOrEqual(t, defaultTestEvictSize, mt1.cache.cachedNodeCount)
122+
verifyCacheNodeCount(t, mt1)
123+
}
124+
}
125+
}
126+
127+
// smallPageMemoryCommitter is an InMemoryCommitter, which has a custom page size, and knows how to "fail" per request.
128+
type smallPageMemoryCommitter struct {
129+
InMemoryCommitter
130+
pageSize int64
131+
failStore int
132+
failLoad int
133+
}
134+
135+
// GetNodesCountPerPage returns the page size ( number of nodes per page )
136+
func (spmc *smallPageMemoryCommitter) GetNodesCountPerPage() (pageSize int64) {
137+
return spmc.pageSize
138+
}
139+
140+
// StorePage stores a single page in an in-memory persistence.
141+
func (spmc *smallPageMemoryCommitter) StorePage(page uint64, content []byte) error {
142+
if spmc.failStore > 0 {
143+
spmc.failStore--
144+
return fmt.Errorf("failStore>0")
145+
}
146+
return spmc.InMemoryCommitter.StorePage(page, content)
147+
}
148+
149+
// LoadPage load a single page from an in-memory persistence.
150+
func (spmc *smallPageMemoryCommitter) LoadPage(page uint64) (content []byte, err error) {
151+
if spmc.failLoad > 0 {
152+
spmc.failLoad--
153+
return nil, fmt.Errorf("failLoad>0")
154+
}
155+
return spmc.InMemoryCommitter.LoadPage(page)
156+
}
157+
158+
func cacheEvictionFuzzer(t *testing.T, hashes []crypto.Digest, pageSize int64, evictSize int) {
159+
var memoryCommitter smallPageMemoryCommitter
160+
memoryCommitter.pageSize = pageSize
161+
mt1, _ := MakeTrie(&memoryCommitter, evictSize)
162+
163+
// add the first 10 hashes.
164+
for i := 0; i < 10; i++ {
165+
mt1.Add(hashes[i][:])
166+
}
167+
168+
for i := 10; i < len(hashes)-10; i++ {
169+
for k := 0; k < int(hashes[i-2][0]%5); k++ {
170+
if hashes[i+k-3][0]%7 == 0 {
171+
memoryCommitter.failLoad++
172+
}
173+
if hashes[i+k-4][0]%7 == 0 {
174+
memoryCommitter.failStore++
175+
}
176+
if hashes[i+k][0]%7 == 0 {
177+
mt1.Delete(hashes[i+k-int(hashes[i][0]%7)][:])
178+
}
179+
mt1.Add(hashes[i+k+3-int(hashes[i+k-1][0]%7)][:])
180+
}
181+
if hashes[i][0]%5 == 0 {
182+
verifyCacheNodeCount(t, mt1)
183+
mt1.Evict(true)
184+
verifyCacheNodeCount(t, mt1)
185+
}
186+
}
187+
}
188+
189+
// TestCacheEvictionFuzzer generates bursts of random Add/Delete operations on the trie, and
190+
// testing the correctness of the cache internal buffers priodically.
191+
func TestCacheEvictionFuzzer(t *testing.T) {
192+
// create 2000 hashes.
193+
leafsCount := 2000
194+
hashes := make([]crypto.Digest, leafsCount)
195+
for i := 0; i < len(hashes); i++ {
196+
hashes[i] = crypto.Hash([]byte{byte(i % 256), byte((i / 256) % 256), byte(i / 65536)})
197+
}
198+
for _, pageSize := range []int64{2, 3, 8, 12, 17} {
199+
for _, evictSize := range []int{5, 10, 13, 30} {
200+
t.Run(fmt.Sprintf("Fuzzer-%d-%d", pageSize, evictSize), func(t *testing.T) {
201+
cacheEvictionFuzzer(t, hashes, pageSize, evictSize)
202+
})
203+
}
204+
}
205+
}
206+
207+
// TestCacheEvictionFuzzer generates bursts of random Add/Delete operations on the trie, and
208+
// testing the correctness of the cache internal buffers priodically.
209+
func TestCacheEvictionFuzzer2(t *testing.T) {
210+
// create 1000 hashes.
211+
leafsCount := 1000
212+
hashes := make([]crypto.Digest, leafsCount)
213+
for i := 0; i < len(hashes); i++ {
214+
hashes[i] = crypto.Hash([]byte{byte(i % 256), byte((i / 256) % 256), byte(i / 65536)})
215+
}
216+
for i := 0; i < 80; i++ {
217+
pageSize := int64(1 + crypto.RandUint64()%101)
218+
evictSize := int(1 + crypto.RandUint64()%37)
219+
hashesCount := uint64(100) + crypto.RandUint64()%uint64(leafsCount-100)
220+
t.Run(fmt.Sprintf("Fuzzer-%d-%d", pageSize, evictSize), func(t *testing.T) {
221+
cacheEvictionFuzzer(t, hashes[:hashesCount], pageSize, evictSize)
222+
})
223+
}
224+
}

crypto/merkletrie/trie_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ func TestRandomAddingAndRemoving(t *testing.T) {
133133
if (i % (1 + int(processesHash[0]))) == 42 {
134134
err := mt.Commit()
135135
require.NoError(t, err)
136+
verifyCacheNodeCount(t, mt)
136137
}
137138
}
138139
}

daemon/algod/api/algod.oas2.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@
170170
"get": {
171171
"description": "Get the list of pending transactions by address, sorted by priority, in decreasing order, truncated at the end at MAX. If MAX = 0, returns all pending transactions.\n",
172172
"produces": [
173-
"application/json"
173+
"application/json",
174+
"application/msgpack"
174175
],
175176
"schemes": [
176177
"http"
@@ -663,7 +664,8 @@
663664
"get": {
664665
"description": "Given a transaction id of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round \u003e 0) - transaction still in the pool (committed round = 0, pool error = \"\") - transaction removed from pool due to error (committed round = 0, pool error != \"\")\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.\n",
665666
"produces": [
666-
"application/json"
667+
"application/json",
668+
"application/msgpack"
667669
],
668670
"schemes": [
669671
"http"

0 commit comments

Comments
 (0)