@ -0,0 +1,80 @@ | |||
# ADR 015: Crypto encoding | |||
## Context | |||
We must standardize our method for encoding public keys and signatures on chain. | |||
Currently we amino encode the public keys and signatures. | |||
The reason we are using amino here is primarily due to ease of support in | |||
parsing for other languages. | |||
We don't need its upgradability properties in cryptosystems, as a change in | |||
the crypto that requires adapting the encoding, likely warrants being deemed | |||
a new cryptosystem. | |||
(I.e. using new public parameters) | |||
## Decision | |||
### Public keys | |||
For public keys, we will continue to use amino encoding on the canonical | |||
representation of the pubkey. | |||
(Canonical as defined by the cryptosystem itself) | |||
This has two significant drawbacks. | |||
Amino encoding is less space-efficient, due to requiring support for upgradability. | |||
Amino encoding support requires forking protobuf and adding this new interface support | |||
option in the langauge of choice. | |||
The reason for continuing to use amino however is that people can create code | |||
more easily in languages that already have an up to date amino library. | |||
It is possible that this will change in the future, if it is deemed that | |||
requiring amino for interacting with tendermint cryptography is unneccessary. | |||
The arguments for space efficiency here are refuted on the basis that there are | |||
far more egregious wastages of space in the SDK. | |||
The space requirement of the public keys doesn't cause many problems beyond | |||
increasing the space attached to each validator / account. | |||
The alternative to using amino here would be for us to create an enum type. | |||
Switching to just an enum type is worthy of investigation post-launch. | |||
For referrence, part of amino encoding interfaces is basically a 4 byte enum | |||
type definition. | |||
Enum types would just change that 4 bytes to be a varuint, and it would remove | |||
the protobuf overhead, but it would be hard to integrate into the existing API. | |||
### Signatures | |||
Signatures should be switched to be `[]byte`. | |||
Spatial efficiency in the signatures is quite important, | |||
as it directly affects the gas cost of every transaction, | |||
and the throughput of the chain. | |||
Signatures don't need to encode what type they are for (unlike public keys) | |||
since public keys must already be known. | |||
Therefore we can validate the signature without needing to encode its type. | |||
When placed in state, signatures will still be amino encoded, but it will be the | |||
primitive type `[]byte` getting encoded. | |||
#### Ed25519 | |||
Use the canonical representation for signatures. | |||
#### Secp256k1 | |||
There isn't a clear canonical representation here. | |||
Signatures have two elements `r,s`. | |||
We should encode these bytes as `r || s`, where `r` and `s` are both exactly | |||
32 bytes long. | |||
This is basically Ethereum's encoding, but without the leading recovery bit. | |||
## Status | |||
Proposed. The signature section seems to be agreed upon for the most part. | |||
Needs decision on Enum types. | |||
## Consequences | |||
### Positive | |||
* More space efficient signatures | |||
### Negative | |||
* We have an amino dependency for cryptography. | |||
### Neutral | |||
* No change to public keys |
@ -0,0 +1,128 @@ | |||
package common | |||
import ( | |||
fmt "fmt" | |||
"io" | |||
"os" | |||
"path/filepath" | |||
"strconv" | |||
"strings" | |||
"sync" | |||
"time" | |||
) | |||
const ( | |||
atomicWriteFilePrefix = "write-file-atomic-" | |||
// Maximum number of atomic write file conflicts before we start reseeding | |||
// (reduced from golang's default 10 due to using an increased randomness space) | |||
atomicWriteFileMaxNumConflicts = 5 | |||
// Maximum number of attempts to make at writing the write file before giving up | |||
// (reduced from golang's default 10000 due to using an increased randomness space) | |||
atomicWriteFileMaxNumWriteAttempts = 1000 | |||
// LCG constants from Donald Knuth MMIX | |||
// This LCG's has a period equal to 2**64 | |||
lcgA = 6364136223846793005 | |||
lcgC = 1442695040888963407 | |||
// Create in case it doesn't exist and force kernel | |||
// flush, which still leaves the potential of lingering disk cache. | |||
// Never overwrites files | |||
atomicWriteFileFlag = os.O_WRONLY | os.O_CREATE | os.O_SYNC | os.O_TRUNC | os.O_EXCL | |||
) | |||
var ( | |||
atomicWriteFileRand uint64 | |||
atomicWriteFileRandMu sync.Mutex | |||
) | |||
func writeFileRandReseed() uint64 { | |||
// Scale the PID, to minimize the chance that two processes seeded at similar times | |||
// don't get the same seed. Note that PID typically ranges in [0, 2**15), but can be | |||
// up to 2**22 under certain configurations. We left bit-shift the PID by 20, so that | |||
// a PID difference of one corresponds to a time difference of 2048 seconds. | |||
// The important thing here is that now for a seed conflict, they would both have to be on | |||
// the correct nanosecond offset, and second-based offset, which is much less likely than | |||
// just a conflict with the correct nanosecond offset. | |||
return uint64(time.Now().UnixNano() + int64(os.Getpid()<<20)) | |||
} | |||
// Use a fast thread safe LCG for atomic write file names. | |||
// Returns a string corresponding to a 64 bit int. | |||
// If it was a negative int, the leading number is a 0. | |||
func randWriteFileSuffix() string { | |||
atomicWriteFileRandMu.Lock() | |||
r := atomicWriteFileRand | |||
if r == 0 { | |||
r = writeFileRandReseed() | |||
} | |||
// Update randomness according to lcg | |||
r = r*lcgA + lcgC | |||
atomicWriteFileRand = r | |||
atomicWriteFileRandMu.Unlock() | |||
// Can have a negative name, replace this in the following | |||
suffix := strconv.Itoa(int(r)) | |||
if string(suffix[0]) == "-" { | |||
// Replace first "-" with "0". This is purely for UI clarity, | |||
// as otherwhise there would be two `-` in a row. | |||
suffix = strings.Replace(suffix, "-", "0", 1) | |||
} | |||
return suffix | |||
} | |||
// WriteFileAtomic creates a temporary file with data and provided perm and | |||
// swaps it atomically with filename if successful. | |||
func WriteFileAtomic(filename string, data []byte, perm os.FileMode) (err error) { | |||
// This implementation is inspired by the golang stdlibs method of creating | |||
// tempfiles. Notable differences are that we use different flags, a 64 bit LCG | |||
// and handle negatives differently. | |||
// The core reason we can't use golang's TempFile is that we must write | |||
// to the file synchronously, as we need this to persist to disk. | |||
// We also open it in write-only mode, to avoid concerns that arise with read. | |||
var ( | |||
dir = filepath.Dir(filename) | |||
f *os.File | |||
) | |||
nconflict := 0 | |||
// Limit the number of attempts to create a file. Something is seriously | |||
// wrong if it didn't get created after 1000 attempts, and we don't want | |||
// an infinite loop | |||
i := 0 | |||
for ; i < atomicWriteFileMaxNumWriteAttempts; i++ { | |||
name := filepath.Join(dir, atomicWriteFilePrefix+randWriteFileSuffix()) | |||
f, err = os.OpenFile(name, atomicWriteFileFlag, perm) | |||
// If the file already exists, try a new file | |||
if os.IsExist(err) { | |||
// If the files exists too many times, start reseeding as we've | |||
// likely hit another instances seed. | |||
if nconflict++; nconflict > atomicWriteFileMaxNumConflicts { | |||
atomicWriteFileRandMu.Lock() | |||
atomicWriteFileRand = writeFileRandReseed() | |||
atomicWriteFileRandMu.Unlock() | |||
} | |||
continue | |||
} else if err != nil { | |||
return err | |||
} | |||
break | |||
} | |||
if i == atomicWriteFileMaxNumWriteAttempts { | |||
return fmt.Errorf("Could not create atomic write file after %d attempts", i) | |||
} | |||
// Clean up in any case. Defer stacking order is last-in-first-out. | |||
defer os.Remove(f.Name()) | |||
defer f.Close() | |||
if n, err := f.Write(data); err != nil { | |||
return err | |||
} else if n < len(data) { | |||
return io.ErrShortWrite | |||
} | |||
// Close the file before renaming it, otherwise it will cause "The process | |||
// cannot access the file because it is being used by another process." on windows. | |||
f.Close() | |||
return os.Rename(f.Name(), filename) | |||
} |
@ -0,0 +1,138 @@ | |||
package common | |||
// Need access to internal variables, so can't use _test package | |||
import ( | |||
"bytes" | |||
fmt "fmt" | |||
"io/ioutil" | |||
"os" | |||
testing "testing" | |||
"github.com/stretchr/testify/require" | |||
) | |||
func TestWriteFileAtomic(t *testing.T) { | |||
var ( | |||
data = []byte(RandStr(RandIntn(2048))) | |||
old = RandBytes(RandIntn(2048)) | |||
perm os.FileMode = 0600 | |||
) | |||
f, err := ioutil.TempFile("/tmp", "write-atomic-test-") | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
defer os.Remove(f.Name()) | |||
if err = ioutil.WriteFile(f.Name(), old, 0664); err != nil { | |||
t.Fatal(err) | |||
} | |||
if err = WriteFileAtomic(f.Name(), data, perm); err != nil { | |||
t.Fatal(err) | |||
} | |||
rData, err := ioutil.ReadFile(f.Name()) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
if !bytes.Equal(data, rData) { | |||
t.Fatalf("data mismatch: %v != %v", data, rData) | |||
} | |||
stat, err := os.Stat(f.Name()) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
if have, want := stat.Mode().Perm(), perm; have != want { | |||
t.Errorf("have %v, want %v", have, want) | |||
} | |||
} | |||
// This tests atomic write file when there is a single duplicate file. | |||
// Expected behavior is for a new file to be created, and the original write file to be unaltered. | |||
func TestWriteFileAtomicDuplicateFile(t *testing.T) { | |||
var ( | |||
defaultSeed uint64 = 1 | |||
testString = "This is a glorious test string" | |||
expectedString = "Did the test file's string appear here?" | |||
fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt" | |||
) | |||
// Create a file at the seed, and reset the seed. | |||
atomicWriteFileRand = defaultSeed | |||
firstFileRand := randWriteFileSuffix() | |||
atomicWriteFileRand = defaultSeed | |||
fname := "/tmp/" + atomicWriteFilePrefix + firstFileRand | |||
f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777) | |||
defer os.Remove(fname) | |||
// Defer here, in case there is a panic in WriteFileAtomic. | |||
defer os.Remove(fileToWrite) | |||
require.Nil(t, err) | |||
f.WriteString(testString) | |||
WriteFileAtomic(fileToWrite, []byte(expectedString), 0777) | |||
// Check that the first atomic file was untouched | |||
firstAtomicFileBytes, err := ioutil.ReadFile(fname) | |||
require.Nil(t, err, "Error reading first atomic file") | |||
require.Equal(t, []byte(testString), firstAtomicFileBytes, "First atomic file was overwritten") | |||
// Check that the resultant file is correct | |||
resultantFileBytes, err := ioutil.ReadFile(fileToWrite) | |||
require.Nil(t, err, "Error reading resultant file") | |||
require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes") | |||
// Check that the intermediate write file was deleted | |||
// Get the second write files' randomness | |||
atomicWriteFileRand = defaultSeed | |||
_ = randWriteFileSuffix() | |||
secondFileRand := randWriteFileSuffix() | |||
_, err = os.Stat("/tmp/" + atomicWriteFilePrefix + secondFileRand) | |||
require.True(t, os.IsNotExist(err), "Intermittent atomic write file not deleted") | |||
} | |||
// This tests atomic write file when there are many duplicate files. | |||
// Expected behavior is for a new file to be created under a completely new seed, | |||
// and the original write files to be unaltered. | |||
func TestWriteFileAtomicManyDuplicates(t *testing.T) { | |||
var ( | |||
defaultSeed uint64 = 2 | |||
testString = "This is a glorious test string, from file %d" | |||
expectedString = "Did any of the test file's string appear here?" | |||
fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt" | |||
) | |||
// Initialize all of the atomic write files | |||
atomicWriteFileRand = defaultSeed | |||
for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ { | |||
fileRand := randWriteFileSuffix() | |||
fname := "/tmp/" + atomicWriteFilePrefix + fileRand | |||
f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777) | |||
require.Nil(t, err) | |||
f.WriteString(fmt.Sprintf(testString, i)) | |||
defer os.Remove(fname) | |||
} | |||
atomicWriteFileRand = defaultSeed | |||
// Defer here, in case there is a panic in WriteFileAtomic. | |||
defer os.Remove(fileToWrite) | |||
WriteFileAtomic(fileToWrite, []byte(expectedString), 0777) | |||
// Check that all intermittent atomic file were untouched | |||
atomicWriteFileRand = defaultSeed | |||
for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ { | |||
fileRand := randWriteFileSuffix() | |||
fname := "/tmp/" + atomicWriteFilePrefix + fileRand | |||
firstAtomicFileBytes, err := ioutil.ReadFile(fname) | |||
require.Nil(t, err, "Error reading first atomic file") | |||
require.Equal(t, []byte(fmt.Sprintf(testString, i)), firstAtomicFileBytes, | |||
"atomic write file %d was overwritten", i) | |||
} | |||
// Check that the resultant file is correct | |||
resultantFileBytes, err := ioutil.ReadFile(fileToWrite) | |||
require.Nil(t, err, "Error reading resultant file") | |||
require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes") | |||
} |
@ -0,0 +1,32 @@ | |||
9fe4a5a73df12dbd8659b1d9280873fe993caefec6b0ebc2686dd65027148e03,true,80a83ad6afcb6f8175192e41973aed31dd75e3c106f813d986d9567a4865eb2f,96362a04f628a0666d9866147326898bb0847b8db8680263ad19e6336d4eed9e,2632c3fd20f456c5383ed16aa1d56dc7875a2b0fc0d5ff053c3ada8934098c69 | |||
0716764b370d543fee692af03832c16410f0a56e4ddb79604ea093b10bb6f654,false,84f2b1e8658456529a2c324f46c3406c3c6fecd5fbbf9169f60bed8956a8b03d,cba357ae33d7234520d5742102a2a6cdb39b7db59c14a58fa8aadd310127630f,576643a8fcc1a4cf866db900f4a150dbe35d44a1b3ff36e4911565c3fa22fc32 | |||
358dd73aae2c5b7b94b57f950408a3c681e748777ecab2063c8ca51a63588fa8,false,c2e2f664c8ee561af8e1e30553373be4ae23edecc8c6bd762d44b2afb7f2a037,d1563f428ac1c023c15d8082b2503157fe9ecbde4fb3493edd69ebc299b4970c,89fb6c6439b12fe11a4c604b8ad883f7dc76be33df590818fe5eb15ddb01face | |||
0958308bdb583e639dd399a98cd21077d834b4b5e30771275a5a73a62efcc7e0,false,523c0ae97039173566f7ab4b8f271d8d78feef5a432d618e58ced4f80f7c1696,c1b743401c6e4508e62b8245ea7c3252bbad082e10af10e80608084d63877977,d7c52adf12ebc69677aec4bd387b0c5a35570fe61cb7b8ae55f3ab14b1b79be0 | |||
d93d134e72f58f177642ac30f36b2d3cd4720aa7e60feb1296411a9009cf4524,false,47a427bcc1ef6f0ce31dbf343bc8bbf49554b4dd1e2330fd97d0df23ecdbba10,73e23adb7801179349ecf9c8cdf64d71d64a9f1145ba6730e5d029f99eaf8840,a8fdcb77f591bfba7b8483aa15ae7b42054ba68625d51dec005896dfe910281f | |||
6104474c791cda24d952b356fb41a5d273c0ce6cc87d270b1701d0523cd5aa13,true,1cb4397b9e478430321af4647da2ccbef62ff8888542d31cca3f626766c8080f,673b23318826bd31ad1a4995c6e5095c4b092f5598aa0a96381a3e977bc0eaf9,4a25a25c5f75d6cc512f2ba8c1546e6263e9ef8269f0c046c37838cc66aa83e6 | |||
8a6002503c15cab763e27c53fc449f6854a210c95cdd67e4466b0f2cb46b629c,false,f01ff06aef356c87f8d2646ff9ed8b855497c2ca00ea330661d84ef421a67e63,4f59bb23090010614877265a1597f1a142fa97b7208e1d554435763505f36f6a,1aadcb1c8b5993da102cebcb60c545b03197c98137064530840f45d917ad300e | |||
31a57c6b1fe33beb1f7ebbbfc06d58c4f307cd355b6f9753e58f3edec16c7559,false,13e126c4cb240349dccf0dc843977671d34a1daffd0517d06ed66b703344db22,d491431906a306af45ecf9f1977e32d7f65a79f5139f931760416de27554b687,5ea7e8e3d5a30503423341609d360d246b61a9159fc07f253a46e357977cd745 | |||
71a3c79718b824627faeefdce887d9465b353bd962cc5e97c5b5dfedab457ef9,true,e2e8eea547dcee7eafa89ae41f48ab049beac24935fad75258924fd5273d23cb,45d2e839bf36a3616cbe8a9bdbd4e7b288bf5bf1e6e79c07995eb2b18eb2eaff,7ee50e0810bc9f98e56bc46de5da22d84b3efa52fe5d85db4b2344530ef17ed8 | |||
2e9dba2eb4f9019c2628ff5899744469c26caf793636f30ddb76601751aee968,false,8bfc3b314e4468d4e19c9d28b7bfd5b5532263105273b0fe80801f6146313993,b77d2b223e27038f978ab87a725859f6995f903056bdbd594ab04f0b2cbad517,9032be49a9cbcd1de6fee332f8f24ebf545c05e0175b98c564e7d1e69630ae20 | |||
81322b22c835efb26d78051f3a3840a9d01aa558c019ecfa26483b5c5535728c,true,61eacb7e9665e362ef492ef950cea58f8bc67434ab7ee5545139147adf395da4,0f600ef0c358cae938969f434c2ec0ce3be632fdf5246b7bb8ee3ff294036ecd,a7026b4c21fe225ecd775ae81249405c6f492882eb85f3f8e2232f11e515561e | |||
826b86c5e8cb4173ff2d05c48e3537140c5e0f26f7866bbcd4e57616806e1be2,true,ae44dabd077d227c8d898930a7705a2b785c8849121282106c045bb58b66eb36,24b2c1b1e2a9ebe387df6dfb9fbde6c681e4eeb0a33bb1c3df3789087f56ffe3,b37a64ea97431b25cb271c4c8435f6dd97118b35da57168f3c3c269920f7bbc1 | |||
18b5a7b973d4b263072e69515c5b6ed22191c3d6e851aaba872904672f8344ec,true,ce402af2fb93b6ef18cd406f7c437d3cbfb09141b7a02116b1cfbabbf75ad84a,c86bdb1709ef0f4a31a818843660f83338b9db77e262bb7c6546138e51c6046b,11fcd8e59c4e7f6050d3cd332337db794ae31260c159e409af3ed8f4d6523bf4 | |||
26d10c56872b72bb76ae7c7b3f074afb3d4a364e5e3f8c661be9b4f5a522ea75,true,1c9782a8485c4ecb13904ec551a7f9300ecd687abfbe63c91c7fd583f84a7a4d,ae3f4ccd0dfee8b514f67db2e923714d324935b9ae9e488d088ebb79569d8cc4,8139a3ab728b0e765e4d90549ab8eed7e1048a83267eafa7442208a7f627558a | |||
558838dfcfe94105c46a4ade4548e6c96271d33e6c752661356cc66024615bae,true,d5a38625be74177318072cf877f2427ce2327e9b58d2eb134d0ac52c9126572f,dead938f77007e3164b6eee4cd153433d03ca5d9ec64f41aa6b2d6a069edeeda,4a081a356361da429c564cf7ac8e217121bbe8c5ee5c9632bae0b7ddbe94f9d4 | |||
f4a3f6a93a4827a59682fd8bf1a8e4fd9aaff01a337a86e1966c8fff0e746014,true,39a0aea2a8ac7f0524d63e395a25b98fc3844ed039f20b11058019dca2b3840f,6ff53243426ded506d22501ae0f989d9946b86a8bb2550d7ed6e90fdf41d0e7c,8784e728bf12f465ed20dc6f0e1d949a68e5795d4799536427a6f859547b7fd6 | |||
1717020e1c4fca1b4926dba16671c0c04e4f19c621c646cb4525fa533b1c205c,false,b9a909767f3044608b4e314b149a729bef199f8311310e1ecd2072e5659b7194,7baf0ff4b980919cf545312f45234976f0b6c574aac5b772024f73248aad7538,99a18e1e4b039ef3777a8fdd0d9ffaccaf3b4523b6d26adacfe91cc5fcd9977e | |||
de769062be27b2a4248dd5be315960c8d231738417ece670c2d6a1c52877b59e,true,cc6c2086718b21813513894546e85766d34c754e81fd6a19c12fc322ffb9b1c3,5a7da7500191c65a5f1fbb2a6122717edc70ca0469baf2bbbd6ca8255b93c077,8c0d32091dc687f1399c754a617d224742726bece848b50c35b4db5f0469ace7 | |||
7c5549f36767e02ebf49a4616467199459aa6932dcc091f182f822185659559a,true,d8335e606128b0c621ff6cda99dc62babf4a4436c574c5c478c20122712727d0,0a7c673cccd6f7fd4ed1673f7d0f2cb08961faced123ca901b74581d5bdc8b25,16ac1eb2a39384716c7d490272d87e76c10665fdb331e1883435de175ce4460e | |||
ecf8261ebda248dc7796f98987efe1b7be363a59037c9e61044490d08a077610,true,53def80fcdba01367c0ea36459b57409f59a771f57a8259b54f24785e5656b7d,90140870b3b1e84c9dcf7836eac0581b16fe0a40307619d267c6f871e1efce6a,c6d1836b66c1a722a377c7eb058995a0ef8711839c6d6a0cdd6ad1ff70f935a5 | |||
21c0ef76ce0eae9391ceabfb08a861899db55ac4ccf010ed672599669c6938f2,false,8af5482cc015093f261d5b7ce87035dda41d8318b9960b52cca3e5f0d3f61808,f4d5338bcb57262e1034f01ed3858ca1e5d66a73f18588e72f3dc8c6a730be0c,7ba82c2820c95e3354d9a6ab4920ebcd7938ce19e25930fee58439246b0321b1 | |||
05f3b66d6b0fe906137e60b4719083a2465106badedcdae3a4c91c46c5367340,false,e5c9e074e95c2896fa4093830e96e9cf159b8dcba2ead21f37237cf6e9a9aaa2,b3a0a50309b4ca23cd34363fd8df30e73ec4a275973986c2e11a53752eff0a3b,358a62056ff05f27185b9952d291c6346171937f6811cafbacddd82e17010f39 | |||
fef0251cff7c5d1ba0514f1820a8265453365fd9f5bb8a92f955dc007a40e730,true,e35a0aff6e9060a39c15d276a1337f1948d0be0aef81fcd563a6783115b5283d,20a8efe83474253d70e5fd847df0cd26222cd39e9210687b68c0a23b73429108,2989fab4278b32f4f40dc02227ab30e10f62e15ab7aa7382da769b1d084e33df | |||
1b7bb172baa2753ec9c3e81a7a9b4c6ef10f9ed7afcafa975395f095eca63a54,false,a98257203987d0c4d260d8feef841466977276612e268b69b5ce4191af161b29,ea177a20d6c1f73f9667090568f9197943037d6586f7e2d6b7b81756fc71df5f,844eff318ef4c6ee45f158c1946ff999e40ffac70883ab6d6b90995f246e69a2 | |||
5ee9b60a25753066d0ecc1155ca6afcc6b853ba558c9533c134a93b82e756856,true,9889460b95ca9545864a4a5194891b7d475362428d6d797532da10bf1fc92076,a7a96739abd8eceb6751afc98df68e29f7af16fbfda3d4710df9c35b6dcdb4d5,998326285c90a2ea2e1f6c6dac79530742645e3dd1b2b42a0733388a99cab81b | |||
a102613781872f88a949d82cb5efcc2e0f437010a950d71b87929ecb480af3b3,false,e099080a55b9b29ccecbbb0d91dbe49defcc217efd1de0588e0836ce5970d327,319293b8660a3cea9879487645ddadda72a5c60079c9154bb0dbb8a0c9cda79e,4d567f1b1a1b304347cf7b129e4c7a05aa57e2bbb8ea335db9e33d05fab12e4d | |||
1d4538180d06f37c43e8caa2d0d80aa7c5d701c8c3e31508704131427837f5cc,true,73afeeb46efc03d2b9f20fc271752528e52b8931287296a7e4367c96bccb32bd,59dc4b69d9ccf6f77715e47fb9bf454f1b90bbd05f1d2bbd07c7d6666f31c91f,ac59d735dfcdc3a0a4ce5a10f09dea8c6afd47de9c0308dc817e3789c8aee963 | |||
e4c480af1b0e3487a331761f64eb3f020a2b8ffa25ad17e00f57aa7ec2c5e84d,true,1145e9f001c70d364e97fcdbc88a2a3d6aecdd975212923820f90a0b215f11f6,b802ac7ef21c8abaeae024c76e3fa70a2a82f73e0bb7c7fe76752ad1742af2e6,0a95876e30617e32ae25acd3af97c37dc075825f800def3f2bf3f68a268744e9 | |||
3a7a83dd657dd6277bcfa957534f40d9b559039aad752066a8d7ed9a6d9c0ab5,false,f90a251ad2338b19cfee6a7965f6f5098136974abb99b3d24553fa6117384978,e422ed7567e5602731b3d980106d0546ef4a4da5eb7175d66a452df12d37bad2,b086bed71dfb6662cb10e2b4fb16a7c22394f488e822fc19697db6077f6caf6f | |||
273e8560c2b1734e863a6542bded7a6fcbfb49a12770bd8866d4863dceea3ae9,false,3b7849a362e7b7ba8c8b8a0cd00df5180604987dbda6c03f37d9a09fdb27fb28,e6cdf4d767df0f411e970da8dda6acd3c2c34ce63908d8a6dbf3715daa0318e4,359a4a39fbdffc808161a48a3ffbe77fc6a03ff52324c22510a42e46c08a6f22 | |||
9b4f8702991be9569b6c0b07a2173104d41325017b27d68fa5af91cdab164c4d,true,598323677db11ece050289f31881ee8caacb59376c7182f9055708b2a4673f84,7675adc1264b6758beb097a991f766f62796f78c1cfa58a4de3d81c36434d3ae,d5d8d610ffd85b04cbe1c73ff5becd5917c513d9625b001f51d486d0dadcefe3 | |||
e1a686ba0169eb97379ebf9d22e073819450ee5ad5f049c8e93016e8d2ec1430,false,ffe461e6075865cde2704aa148fd29bcf0af245803f446cb6153244f25617993,46df6c25fa0344e662490c4da0bddca626644e67e66705840ef08aae35c343fa,e9a56d75acad4272ab0c49ee5919a4e86e6c5695ef065704c1e592d4e7b41a10 |
@ -1,39 +0,0 @@ | |||
package core | |||
import ( | |||
"testing" | |||
"time" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
func TestGetValidatorsWithTimeout(t *testing.T) { | |||
height, vs := getValidatorsWithTimeout( | |||
testValidatorReceiver{}, | |||
time.Millisecond, | |||
) | |||
if height != -1 { | |||
t.Errorf("expected negative height") | |||
} | |||
if len(vs) != 0 { | |||
t.Errorf("expected no validators") | |||
} | |||
} | |||
type testValidatorReceiver struct{} | |||
func (tr testValidatorReceiver) GetValidators() (int64, []*types.Validator) { | |||
vs := []*types.Validator{} | |||
for i := 0; i < 3; i++ { | |||
v, _ := types.RandValidator(true, 10) | |||
vs = append(vs, v) | |||
} | |||
time.Sleep(time.Millisecond) | |||
return 10, vs | |||
} |