@ -1,18 +1,7 @@ | |||
## Description | |||
<!-- Add a description of the changes that this PR introduces and the files that | |||
are the most critical to review. | |||
--> | |||
_Please add a description of the changes that this PR introduces and the files that | |||
are the most critical to review._ | |||
Closes: #XXX | |||
--- | |||
For contributor use: | |||
- [ ] Wrote tests | |||
- [ ] Updated CHANGELOG_PENDING.md | |||
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. | |||
- [ ] Updated relevant documentation (`docs/`) and code comments | |||
- [ ] Re-reviewed `Files changed` in the Github PR explorer | |||
- [ ] Applied Appropriate Labels |
@ -0,0 +1,16 @@ | |||
pullRequestOpened: | | |||
:wave: Thanks for creating a PR! | |||
Before we can merge this PR, please make sure that all the following items have been | |||
checked off. If any of the checklist items are not applicable, please leave them but | |||
write a little note why. | |||
- [ ] Wrote tests | |||
- [ ] Updated CHANGELOG_PENDING.md | |||
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. | |||
- [ ] Updated relevant documentation (`docs/`) and code comments | |||
- [ ] Re-reviewed `Files changed` in the Github PR explorer | |||
- [ ] Applied Appropriate Labels | |||
Thank you for your contribution to Tendermint! :rocket: |
@ -1,95 +0,0 @@ | |||
package merkle | |||
import ( | |||
"bytes" | |||
amino "github.com/tendermint/go-amino" | |||
"github.com/tendermint/tendermint/crypto/tmhash" | |||
"github.com/tendermint/tendermint/libs/kv" | |||
) | |||
// Merkle tree from a map. | |||
// Leaves are `hash(key) | hash(value)`. | |||
// Leaves are sorted before Merkle hashing. | |||
type simpleMap struct { | |||
kvs kv.Pairs | |||
sorted bool | |||
} | |||
func newSimpleMap() *simpleMap { | |||
return &simpleMap{ | |||
kvs: nil, | |||
sorted: false, | |||
} | |||
} | |||
// Set creates a kv pair of the key and the hash of the value, | |||
// and then appends it to simpleMap's kv pairs. | |||
func (sm *simpleMap) Set(key string, value []byte) { | |||
sm.sorted = false | |||
// The value is hashed, so you can | |||
// check for equality with a cached value (say) | |||
// and make a determination to fetch or not. | |||
vhash := tmhash.Sum(value) | |||
sm.kvs = append(sm.kvs, kv.Pair{ | |||
Key: []byte(key), | |||
Value: vhash, | |||
}) | |||
} | |||
// Hash Merkle root hash of items sorted by key | |||
// (UNSTABLE: and by value too if duplicate key). | |||
func (sm *simpleMap) Hash() []byte { | |||
sm.Sort() | |||
return hashKVPairs(sm.kvs) | |||
} | |||
func (sm *simpleMap) Sort() { | |||
if sm.sorted { | |||
return | |||
} | |||
sm.kvs.Sort() | |||
sm.sorted = true | |||
} | |||
// Returns a copy of sorted KVPairs. | |||
// NOTE these contain the hashed key and value. | |||
func (sm *simpleMap) KVPairs() kv.Pairs { | |||
sm.Sort() | |||
kvs := make(kv.Pairs, len(sm.kvs)) | |||
copy(kvs, sm.kvs) | |||
return kvs | |||
} | |||
//---------------------------------------- | |||
// A local extension to KVPair that can be hashed. | |||
// Key and value are length prefixed and concatenated, | |||
// then hashed. | |||
type KVPair kv.Pair | |||
// Bytes returns key || value, with both the | |||
// key and value length prefixed. | |||
func (kv KVPair) Bytes() []byte { | |||
var b bytes.Buffer | |||
err := amino.EncodeByteSlice(&b, kv.Key) | |||
if err != nil { | |||
panic(err) | |||
} | |||
err = amino.EncodeByteSlice(&b, kv.Value) | |||
if err != nil { | |||
panic(err) | |||
} | |||
return b.Bytes() | |||
} | |||
func hashKVPairs(kvs kv.Pairs) []byte { | |||
kvsH := make([][]byte, len(kvs)) | |||
for i, kvp := range kvs { | |||
kvsH[i] = KVPair(kvp).Bytes() | |||
} | |||
return SimpleHashFromByteSlices(kvsH) | |||
} |
@ -1,49 +0,0 @@ | |||
package merkle | |||
import ( | |||
"fmt" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func TestSimpleMap(t *testing.T) { | |||
tests := []struct { | |||
keys []string | |||
values []string // each string gets converted to []byte in test | |||
want string | |||
}{ | |||
{[]string{"key1"}, []string{"value1"}, "a44d3cc7daba1a4600b00a2434b30f8b970652169810d6dfa9fb1793a2189324"}, | |||
{[]string{"key1"}, []string{"value2"}, "0638e99b3445caec9d95c05e1a3fc1487b4ddec6a952ff337080360b0dcc078c"}, | |||
// swap order with 2 keys | |||
{ | |||
[]string{"key1", "key2"}, | |||
[]string{"value1", "value2"}, | |||
"8fd19b19e7bb3f2b3ee0574027d8a5a4cec370464ea2db2fbfa5c7d35bb0cff3", | |||
}, | |||
{ | |||
[]string{"key2", "key1"}, | |||
[]string{"value2", "value1"}, | |||
"8fd19b19e7bb3f2b3ee0574027d8a5a4cec370464ea2db2fbfa5c7d35bb0cff3", | |||
}, | |||
// swap order with 3 keys | |||
{ | |||
[]string{"key1", "key2", "key3"}, | |||
[]string{"value1", "value2", "value3"}, | |||
"1dd674ec6782a0d586a903c9c63326a41cbe56b3bba33ed6ff5b527af6efb3dc", | |||
}, | |||
{ | |||
[]string{"key1", "key3", "key2"}, | |||
[]string{"value1", "value3", "value2"}, | |||
"1dd674ec6782a0d586a903c9c63326a41cbe56b3bba33ed6ff5b527af6efb3dc", | |||
}, | |||
} | |||
for i, tc := range tests { | |||
db := newSimpleMap() | |||
for i := 0; i < len(tc.keys); i++ { | |||
db.Set(tc.keys[i], []byte(tc.values[i])) | |||
} | |||
got := db.Hash() | |||
assert.Equal(t, tc.want, fmt.Sprintf("%x", got), "Hash didn't match on tc %d", i) | |||
} | |||
} |
@ -0,0 +1,38 @@ | |||
/* | |||
Package evidence handles all evidence storage and gossiping from detection to block proposal. | |||
For the different types of evidence refer to the `evidence.go` file in the types package | |||
or https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md. | |||
Gossiping | |||
The core functionality begins with the evidence reactor (see reactor. | |||
go) which operates both the sending and receiving of evidence. | |||
The `Receive` function takes a list of evidence and does the following: | |||
1. Breaks it down into individual evidence if it is `Composite Evidence` | |||
(see types/evidence.go#ConflictingHeadersEvidence) | |||
2. Checks that it does not already have the evidence stored | |||
3. Verifies the evidence against the node's state (see state/validation.go#VerifyEvidence) | |||
4. Stores the evidence to a db and a concurrent list | |||
The gossiping of evidence is initiated when a peer is added which starts a go routine to broadcast currently | |||
uncommitted evidence at intervals of 60 seconds (set by the by broadcastEvidenceIntervalS). | |||
It uses a concurrent list to store the evidence and before sending verifies that each evidence is still valid in the | |||
sense that it has not exceeded the max evidence age and height (see types/params.go#EvidenceParams). | |||
Proposing | |||
When a new block is being proposed (in state/execution.go#CreateProposalBlock), | |||
`PendingEvidence(maxNum)` is called to send up to the maxNum number of uncommitted evidence, from the evidence store, | |||
based on a priority that is a product of the age of the evidence and the voting power of the malicious validator. | |||
Once the proposed evidence is submitted, | |||
the evidence is marked as committed and is moved from the broadcasted set to the committed set ( | |||
the committed set is used to verify whether new evidence has actually already been submitted). | |||
As a result it is also removed from the concurrent list so that it is no longer gossiped. | |||
*/ | |||
package evidence |
@ -1,81 +0,0 @@ | |||
package privval | |||
import ( | |||
"io/ioutil" | |||
"os" | |||
"github.com/tendermint/tendermint/crypto" | |||
"github.com/tendermint/tendermint/libs/bytes" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// OldFilePV is the old version of the FilePV, pre v0.28.0. | |||
// Deprecated: Use FilePV instead. | |||
type OldFilePV struct { | |||
Address types.Address `json:"address"` | |||
PubKey crypto.PubKey `json:"pub_key"` | |||
LastHeight int64 `json:"last_height"` | |||
LastRound int `json:"last_round"` | |||
LastStep int8 `json:"last_step"` | |||
LastSignature []byte `json:"last_signature,omitempty"` | |||
LastSignBytes bytes.HexBytes `json:"last_signbytes,omitempty"` | |||
PrivKey crypto.PrivKey `json:"priv_key"` | |||
filePath string | |||
} | |||
// LoadOldFilePV loads an OldFilePV from the filePath. | |||
func LoadOldFilePV(filePath string) (*OldFilePV, error) { | |||
pvJSONBytes, err := ioutil.ReadFile(filePath) | |||
if err != nil { | |||
return nil, err | |||
} | |||
pv := &OldFilePV{} | |||
err = cdc.UnmarshalJSON(pvJSONBytes, &pv) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// overwrite pubkey and address for convenience | |||
pv.PubKey = pv.PrivKey.PubKey() | |||
pv.Address = pv.PubKey.Address() | |||
pv.filePath = filePath | |||
return pv, nil | |||
} | |||
// Upgrade convets the OldFilePV to the new FilePV, separating the immutable and mutable components, | |||
// and persisting them to the keyFilePath and stateFilePath, respectively. | |||
// It renames the original file by adding ".bak". | |||
func (oldFilePV *OldFilePV) Upgrade(keyFilePath, stateFilePath string) *FilePV { | |||
privKey := oldFilePV.PrivKey | |||
pvKey := FilePVKey{ | |||
PrivKey: privKey, | |||
PubKey: privKey.PubKey(), | |||
Address: privKey.PubKey().Address(), | |||
filePath: keyFilePath, | |||
} | |||
pvState := FilePVLastSignState{ | |||
Height: oldFilePV.LastHeight, | |||
Round: oldFilePV.LastRound, | |||
Step: oldFilePV.LastStep, | |||
Signature: oldFilePV.LastSignature, | |||
SignBytes: oldFilePV.LastSignBytes, | |||
filePath: stateFilePath, | |||
} | |||
// Save the new PV files | |||
pv := &FilePV{ | |||
Key: pvKey, | |||
LastSignState: pvState, | |||
} | |||
pv.Save() | |||
// Rename the old PV file | |||
err := os.Rename(oldFilePV.filePath, oldFilePV.filePath+".bak") | |||
if err != nil { | |||
panic(err) | |||
} | |||
return pv | |||
} |
@ -1,84 +0,0 @@ | |||
package privval_test | |||
import ( | |||
"io/ioutil" | |||
"os" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/tendermint/privval" | |||
) | |||
const lastSignBytes = "750802110500000000000000220B08B398F3E00510F48DA6402A480A20F" + | |||
"C258973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4" + | |||
"CAD312240A20C971B286ACB8AAA6FCA0365EB0A660B189EDC08B46B5AF2" + | |||
"995DEFA51A28D215B10013211746573742D636861696E2D533245415533" | |||
const oldPrivvalContent = `{ | |||
"address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D", | |||
"pub_key": { | |||
"type": "tendermint/PubKeyEd25519", | |||
"value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0=" | |||
}, | |||
"last_height": "5", | |||
"last_round": "0", | |||
"last_step": 3, | |||
"last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==", | |||
"last_signbytes": "` + lastSignBytes + `", | |||
"priv_key": { | |||
"type": "tendermint/PrivKeyEd25519", | |||
"value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ==" | |||
} | |||
}` | |||
func TestLoadAndUpgrade(t *testing.T) { | |||
oldFilePath := initTmpOldFile(t) | |||
defer os.Remove(oldFilePath) | |||
newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json") | |||
defer os.Remove(newStateFile.Name()) | |||
require.NoError(t, err) | |||
newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json") | |||
defer os.Remove(newKeyFile.Name()) | |||
require.NoError(t, err) | |||
oldPV, err := privval.LoadOldFilePV(oldFilePath) | |||
assert.NoError(t, err) | |||
newPV := oldPV.Upgrade(newKeyFile.Name(), newStateFile.Name()) | |||
assertEqualPV(t, oldPV, newPV) | |||
assert.NoError(t, err) | |||
upgradedPV := privval.LoadFilePV(newKeyFile.Name(), newStateFile.Name()) | |||
assertEqualPV(t, oldPV, upgradedPV) | |||
oldPV, err = privval.LoadOldFilePV(oldFilePath + ".bak") | |||
require.NoError(t, err) | |||
assertEqualPV(t, oldPV, upgradedPV) | |||
} | |||
func assertEqualPV(t *testing.T, oldPV *privval.OldFilePV, newPV *privval.FilePV) { | |||
assert.Equal(t, oldPV.Address, newPV.Key.Address) | |||
assert.Equal(t, oldPV.Address, newPV.GetAddress()) | |||
assert.Equal(t, oldPV.PubKey, newPV.Key.PubKey) | |||
npv, err := newPV.GetPubKey() | |||
require.NoError(t, err) | |||
assert.Equal(t, oldPV.PubKey, npv) | |||
assert.Equal(t, oldPV.PrivKey, newPV.Key.PrivKey) | |||
assert.Equal(t, oldPV.LastHeight, newPV.LastSignState.Height) | |||
assert.Equal(t, oldPV.LastRound, newPV.LastSignState.Round) | |||
assert.Equal(t, oldPV.LastSignature, newPV.LastSignState.Signature) | |||
assert.Equal(t, oldPV.LastSignBytes, newPV.LastSignState.SignBytes) | |||
assert.Equal(t, oldPV.LastStep, newPV.LastSignState.Step) | |||
} | |||
func initTmpOldFile(t *testing.T) string { | |||
tmpFile, err := ioutil.TempFile("", "priv_validator_*.json") | |||
require.NoError(t, err) | |||
t.Logf("created test file %s", tmpFile.Name()) | |||
_, err = tmpFile.WriteString(oldPrivvalContent) | |||
require.NoError(t, err) | |||
return tmpFile.Name() | |||
} |
@ -1,45 +0,0 @@ | |||
package main | |||
import ( | |||
"fmt" | |||
"os" | |||
"github.com/tendermint/tendermint/libs/log" | |||
"github.com/tendermint/tendermint/privval" | |||
) | |||
var ( | |||
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) | |||
) | |||
func main() { | |||
args := os.Args[1:] | |||
if len(args) != 3 { | |||
fmt.Println("Expected three args: <old path> <new key path> <new state path>") | |||
fmt.Println( | |||
"Eg. ~/.tendermint/config/priv_validator.json" + | |||
" ~/.tendermint/config/priv_validator_key.json" + | |||
" ~/.tendermint/data/priv_validator_state.json", | |||
) | |||
os.Exit(1) | |||
} | |||
err := loadAndUpgrade(args[0], args[1], args[2]) | |||
if err != nil { | |||
fmt.Println(err) | |||
os.Exit(1) | |||
} | |||
} | |||
func loadAndUpgrade(oldPVPath, newPVKeyPath, newPVStatePath string) error { | |||
oldPV, err := privval.LoadOldFilePV(oldPVPath) | |||
if err != nil { | |||
return fmt.Errorf("error reading OldPrivValidator from %v: %v", oldPVPath, err) | |||
} | |||
logger.Info("Upgrading PrivValidator file", | |||
"old", oldPVPath, | |||
"newKey", newPVKeyPath, | |||
"newState", newPVStatePath, | |||
) | |||
oldPV.Upgrade(newPVKeyPath, newPVStatePath) | |||
return nil | |||
} |
@ -1,129 +0,0 @@ | |||
package main | |||
import ( | |||
"fmt" | |||
"io/ioutil" | |||
"os" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/tendermint/privval" | |||
) | |||
const lastSignBytes = "750802110500000000000000220B08B398F3E00510F48DA6402A480A20FC25" + | |||
"8973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4CAD312240A20C971B286ACB8AA" + | |||
"A6FCA0365EB0A660B189EDC08B46B5AF2995DEFA51A28D215B10013211746573742D636861696E2D533245415533" | |||
const oldPrivvalContent = `{ | |||
"address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D", | |||
"pub_key": { | |||
"type": "tendermint/PubKeyEd25519", | |||
"value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0=" | |||
}, | |||
"last_height": "5", | |||
"last_round": "0", | |||
"last_step": 3, | |||
"last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==", | |||
"last_signbytes": "` + lastSignBytes + `", | |||
"priv_key": { | |||
"type": "tendermint/PrivKeyEd25519", | |||
"value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ==" | |||
} | |||
}` | |||
func TestLoadAndUpgrade(t *testing.T) { | |||
oldFilePath := initTmpOldFile(t) | |||
defer os.Remove(oldFilePath) | |||
newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json") | |||
defer os.Remove(newStateFile.Name()) | |||
require.NoError(t, err) | |||
newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json") | |||
defer os.Remove(newKeyFile.Name()) | |||
require.NoError(t, err) | |||
emptyOldFile, err := ioutil.TempFile("", "priv_validator_empty*.json") | |||
require.NoError(t, err) | |||
defer os.Remove(emptyOldFile.Name()) | |||
type args struct { | |||
oldPVPath string | |||
newPVKeyPath string | |||
newPVStatePath string | |||
} | |||
tests := []struct { | |||
name string | |||
args args | |||
wantErr bool | |||
wantPanic bool | |||
}{ | |||
{"successful upgrade", | |||
args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()}, | |||
false, false, | |||
}, | |||
{"unsuccessful upgrade: empty old privval file", | |||
args{oldPVPath: emptyOldFile.Name(), newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()}, | |||
true, false, | |||
}, | |||
{"unsuccessful upgrade: invalid new paths (1/3)", | |||
args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: newStateFile.Name()}, | |||
false, true, | |||
}, | |||
{"unsuccessful upgrade: invalid new paths (2/3)", | |||
args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: ""}, | |||
false, true, | |||
}, | |||
{"unsuccessful upgrade: invalid new paths (3/3)", | |||
args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: ""}, | |||
false, true, | |||
}, | |||
} | |||
for _, tt := range tests { | |||
tt := tt | |||
t.Run(tt.name, func(t *testing.T) { | |||
// need to re-write the file everytime because upgrading renames it | |||
err := ioutil.WriteFile(oldFilePath, []byte(oldPrivvalContent), 0600) | |||
require.NoError(t, err) | |||
if tt.wantPanic { | |||
require.Panics(t, func() { loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) }) | |||
} else { | |||
err = loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) | |||
if tt.wantErr { | |||
assert.Error(t, err) | |||
fmt.Println("ERR", err) | |||
} else { | |||
assert.NoError(t, err) | |||
upgradedPV := privval.LoadFilePV(tt.args.newPVKeyPath, tt.args.newPVStatePath) | |||
oldPV, err := privval.LoadOldFilePV(tt.args.oldPVPath + ".bak") | |||
require.NoError(t, err) | |||
assert.Equal(t, oldPV.Address, upgradedPV.Key.Address) | |||
assert.Equal(t, oldPV.Address, upgradedPV.GetAddress()) | |||
assert.Equal(t, oldPV.PubKey, upgradedPV.Key.PubKey) | |||
upv, err := upgradedPV.GetPubKey() | |||
require.NoError(t, err) | |||
assert.Equal(t, oldPV.PubKey, upv) | |||
assert.Equal(t, oldPV.PrivKey, upgradedPV.Key.PrivKey) | |||
assert.Equal(t, oldPV.LastHeight, upgradedPV.LastSignState.Height) | |||
assert.Equal(t, oldPV.LastRound, upgradedPV.LastSignState.Round) | |||
assert.Equal(t, oldPV.LastSignature, upgradedPV.LastSignState.Signature) | |||
assert.Equal(t, oldPV.LastSignBytes, upgradedPV.LastSignState.SignBytes) | |||
assert.Equal(t, oldPV.LastStep, upgradedPV.LastSignState.Step) | |||
} | |||
} | |||
}) | |||
} | |||
} | |||
func initTmpOldFile(t *testing.T) string { | |||
tmpfile, err := ioutil.TempFile("", "priv_validator_*.json") | |||
require.NoError(t, err) | |||
t.Logf("created test file %s", tmpfile.Name()) | |||
_, err = tmpfile.WriteString(oldPrivvalContent) | |||
require.NoError(t, err) | |||
return tmpfile.Name() | |||
} |