diff --git a/account/account.go b/account/account.go new file mode 100644 index 00000000..fa68167f --- /dev/null +++ b/account/account.go @@ -0,0 +1,19 @@ +package account + +import ( + "bytes" + "io" +) + +type Signable interface { + WriteSignBytes(w io.Writer, n *int64, err *error) +} + +func SignBytes(o Signable) []byte { + buf, n, err := new(bytes.Buffer), new(int64), new(error) + o.WriteSignBytes(buf, n, err) + if *err != nil { + panic(err) + } + return buf.Bytes() +} diff --git a/account/privkey.go b/account/privkey.go new file mode 100644 index 00000000..92b2d269 --- /dev/null +++ b/account/privkey.go @@ -0,0 +1,62 @@ +package account + +import ( + "errors" + "io" + "reflect" + + "github.com/tendermint/go-ed25519" + . "github.com/tendermint/tendermint/binary" + . "github.com/tendermint/tendermint/common" +) + +/* +Each account has an PrivKey which determines access to funds of the account. +Transaction inputs include Signatures of the account's associated PrivKey. +*/ +type PrivKey interface { + Sign(msg []byte) Signature +} + +const ( + PrivKeyTypeEd25519 = byte(0x01) +) + +func PrivKeyDecoder(r io.Reader, n *int64, err *error) interface{} { + switch t := ReadByte(r, n, err); t { + case PrivKeyTypeEd25519: + return ReadBinary(&PrivKeyEd25519{}, r, n, err) + default: + *err = Errorf("Unknown PrivKey type %X", t) + return nil + } +} + +var _ = RegisterType(&TypeInfo{ + Type: reflect.TypeOf((*PrivKey)(nil)).Elem(), + Decoder: PrivKeyDecoder, +}) + +//------------------------------------- + +type PrivKeyEd25519 struct { + PubKey []byte + PrivKey []byte +} + +func (key PrivKeyEd25519) TypeByte() byte { return PrivKeyTypeEd25519 } + +func (key PrivKeyEd25519) ValidateBasic() error { + if len(key.PubKey) != ed25519.PublicKeySize { + return errors.New("Invalid PrivKeyEd25519 pubkey size") + } + if len(key.PrivKey) != ed25519.PrivateKeySize { + return errors.New("Invalid PrivKeyEd25519 privkey size") + } + return nil +} + +func (key PrivKeyEd25519) Sign(msg []byte) Signature { + signature := ed25519.SignMessage(msg, key.PrivKey, key.PubKey) + return SignatureEd25519{signature} +} diff --git a/account/pubkey.go b/account/pubkey.go new file mode 100644 index 00000000..8f7c3c74 --- /dev/null +++ b/account/pubkey.go @@ -0,0 +1,87 @@ +package account + +import ( + "errors" + "io" + "reflect" + + "github.com/tendermint/go-ed25519" + . "github.com/tendermint/tendermint/binary" + . "github.com/tendermint/tendermint/common" +) + +/* +Each account has an PubKey which determines access to funds of the account. +Transaction inputs include Signatures of the account's associated PubKey. +*/ +type PubKey interface { + Address() []byte + VerifyBytes(msg []byte, sig Signature) bool +} + +const ( + PubKeyTypeUnknown = byte(0x00) + PubKeyTypeEd25519 = byte(0x01) +) + +func PubKeyDecoder(r io.Reader, n *int64, err *error) interface{} { + switch t := ReadByte(r, n, err); t { + case PubKeyTypeUnknown: + return PubKeyUnknown{} + case PubKeyTypeEd25519: + return ReadBinary(&PubKeyEd25519{}, r, n, err) + default: + *err = Errorf("Unknown PubKey type %X", t) + return nil + } +} + +var _ = RegisterType(&TypeInfo{ + Type: reflect.TypeOf((*PubKey)(nil)).Elem(), + Decoder: PubKeyDecoder, +}) + +//------------------------------------- + +type PubKeyUnknown struct { +} + +func (key PubKeyUnknown) TypeByte() byte { return PubKeyTypeUnknown } + +func (key PubKeyUnknown) Address() []byte { + panic("PubKeyUnknown has no address") +} + +func (key PubKeyUnknown) VerifyBytes(msg []byte, sig_ Signature) bool { + panic("PubKeyUnknown cannot verify messages") +} + +//------------------------------------- + +type PubKeyEd25519 struct { + PubKey []byte +} + +func (key PubKeyEd25519) TypeByte() byte { return PubKeyTypeEd25519 } + +func (key PubKeyEd25519) Address() []byte { return BinaryRipemd160(key.PubKey) } + +func (key PubKeyEd25519) ValidateBasic() error { + if len(key.PubKey) != ed25519.PublicKeySize { + return errors.New("Invalid PubKeyEd25519 key size") + } + return nil +} + +func (key PubKeyEd25519) VerifyBytes(msg []byte, sig_ Signature) bool { + sig, ok := sig_.(SignatureEd25519) + if !ok { + panic("PubKeyEd25519 expects an SignatureEd25519 signature") + } + v1 := &ed25519.Verify{ + Message: msg, + PubKey: key.PubKey, + Signature: []byte(sig.Bytes), + } + return ed25519.VerifyBatch([]*ed25519.Verify{v1}) +} diff --git a/account/signature.go b/account/signature.go new file mode 100644 index 00000000..a4ddf07a --- /dev/null +++ b/account/signature.go @@ -0,0 +1,52 @@ +package account + +import ( + "errors" + "io" + "reflect" + + "github.com/tendermint/go-ed25519" + . "github.com/tendermint/tendermint/binary" + . "github.com/tendermint/tendermint/common" +) + +type Signature interface { +} + +const ( + SignatureTypeEd25519 = byte(0x01) +) + +func SignatureDecoder(r io.Reader, n *int64, err *error) interface{} { + switch t := ReadByte(r, n, err); t { + case SignatureTypeEd25519: + return ReadBinary(&SignatureEd25519{}, r, n, err) + default: + *err = Errorf("Unknown Signature type %X", t) + return nil + } +} + +var _ = RegisterType(&TypeInfo{ + Type: reflect.TypeOf((*Signature)(nil)).Elem(), + Decoder: SignatureDecoder, +}) + +//------------------------------------- + +type SignatureEd25519 struct { + Bytes []byte +} + +func (sig SignatureEd25519) TypeByte() byte { return SignatureTypeEd25519 } + +func (sig SignatureEd25519) ValidateBasic() error { + if len(sig.Bytes) != ed25519.SignatureSize { + return errors.New("Invalid SignatureEd25519 signature size") + } + return nil +} + +func (sig SignatureEd25519) IsZero() bool { + return len(sig.Bytes) == 0 +} diff --git a/state/account_test.go b/account/signature_test.go similarity index 97% rename from state/account_test.go rename to account/signature_test.go index 20ce6021..cb379741 100644 --- a/state/account_test.go +++ b/account/signature_test.go @@ -1,4 +1,4 @@ -package state +package account import ( . "github.com/tendermint/tendermint/common" diff --git a/binary/binary.go b/binary/binary.go index 338385e2..7f5dd69b 100644 --- a/binary/binary.go +++ b/binary/binary.go @@ -1,23 +1,31 @@ package binary -import "io" +import ( + "io" + "reflect" +) -type Binary interface { - WriteTo(w io.Writer) (int64, error) +func ReadBinary(o interface{}, r io.Reader, n *int64, err *error) interface{} { + rv, rt := reflect.ValueOf(o), reflect.TypeOf(o) + if rv.Kind() == reflect.Ptr { + readReflect(rv.Elem(), rt.Elem(), r, n, err) + return o + } else { + ptrRv := reflect.New(rt) + readReflect(ptrRv.Elem(), rt, r, n, err) + return ptrRv.Elem() + } } -func WriteBinary(w io.Writer, b Binary, n *int64, err *error) { - if *err != nil { - return - } - n_, err_ := b.WriteTo(w) - *n += int64(n_) - *err = err_ +func WriteBinary(o interface{}, w io.Writer, n *int64, err *error) { + rv := reflect.ValueOf(o) + rt := reflect.TypeOf(o) + writeReflect(rv, rt, w, n, err) } // Write all of bz to w // Increment n and set err accordingly. -func WriteTo(w io.Writer, bz []byte, n *int64, err *error) { +func WriteTo(bz []byte, w io.Writer, n *int64, err *error) { if *err != nil { return } @@ -28,7 +36,7 @@ func WriteTo(w io.Writer, bz []byte, n *int64, err *error) { // Read len(buf) from r // Increment n and set err accordingly. -func ReadFull(r io.Reader, buf []byte, n *int64, err *error) { +func ReadFull(buf []byte, r io.Reader, n *int64, err *error) { if *err != nil { return } diff --git a/binary/byteslice.go b/binary/byteslice.go index e31a5b1e..7f448ced 100644 --- a/binary/byteslice.go +++ b/binary/byteslice.go @@ -4,27 +4,27 @@ import ( "io" ) -func WriteByteSlice(w io.Writer, bz []byte, n *int64, err *error) { - WriteUInt32(w, uint32(len(bz)), n, err) - WriteTo(w, bz, n, err) +func WriteByteSlice(bz []byte, w io.Writer, n *int64, err *error) { + WriteUVarInt(uint(len(bz)), w, n, err) + WriteTo(bz, w, n, err) } func ReadByteSlice(r io.Reader, n *int64, err *error) []byte { - length := ReadUInt32(r, n, err) + length := ReadUVarInt(r, n, err) if *err != nil { return nil } buf := make([]byte, int(length)) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return buf } //----------------------------------------------------------------------------- -func WriteByteSlices(w io.Writer, bzz [][]byte, n *int64, err *error) { - WriteUInt32(w, uint32(len(bzz)), n, err) +func WriteByteSlices(bzz [][]byte, w io.Writer, n *int64, err *error) { + WriteUVarInt(uint(len(bzz)), w, n, err) for _, bz := range bzz { - WriteByteSlice(w, bz, n, err) + WriteByteSlice(bz, w, n, err) if *err != nil { return } @@ -32,12 +32,12 @@ func WriteByteSlices(w io.Writer, bzz [][]byte, n *int64, err *error) { } func ReadByteSlices(r io.Reader, n *int64, err *error) [][]byte { - length := ReadUInt32(r, n, err) + length := ReadUVarInt(r, n, err) if *err != nil { return nil } bzz := make([][]byte, length) - for i := uint32(0); i < length; i++ { + for i := uint(0); i < length; i++ { bz := ReadByteSlice(r, n, err) if *err != nil { return nil diff --git a/binary/codec.go b/binary/codec.go index 6dce295a..2f94305d 100644 --- a/binary/codec.go +++ b/binary/codec.go @@ -6,10 +6,14 @@ import ( "time" ) -type Codec interface { - Encode(o interface{}, w io.Writer, n *int64, err *error) - Decode(r io.Reader, n *int64, err *error) interface{} - Compare(o1 interface{}, o2 interface{}) int +type Encoder func(o interface{}, w io.Writer, n *int64, err *error) +type Decoder func(r io.Reader, n *int64, err *error) interface{} +type Comparator func(o1 interface{}, o2 interface{}) int + +type Codec struct { + Encode Encoder + Decode Decoder + Compare Comparator } const ( @@ -29,62 +33,58 @@ const ( typeTime = byte(0x20) ) -var BasicCodec = basicCodec{} - -type basicCodec struct{} - -func (bc basicCodec) Encode(o interface{}, w io.Writer, n *int64, err *error) { +func BasicCodecEncoder(o interface{}, w io.Writer, n *int64, err *error) { switch o.(type) { case nil: panic("nil type unsupported") case byte: - WriteByte(w, typeByte, n, err) - WriteByte(w, o.(byte), n, err) + WriteByte(typeByte, w, n, err) + WriteByte(o.(byte), w, n, err) case int8: - WriteByte(w, typeInt8, n, err) - WriteInt8(w, o.(int8), n, err) + WriteByte(typeInt8, w, n, err) + WriteInt8(o.(int8), w, n, err) //case uint8: - // WriteByte(w, typeUInt8, n, err) - // WriteUInt8(w, o.(uint8), n, err) + // WriteByte( typeUInt8, w, n, err) + // WriteUInt8( o.(uint8), w, n, err) case int16: - WriteByte(w, typeInt16, n, err) - WriteInt16(w, o.(int16), n, err) + WriteByte(typeInt16, w, n, err) + WriteInt16(o.(int16), w, n, err) case uint16: - WriteByte(w, typeUInt16, n, err) - WriteUInt16(w, o.(uint16), n, err) + WriteByte(typeUInt16, w, n, err) + WriteUInt16(o.(uint16), w, n, err) case int32: - WriteByte(w, typeInt32, n, err) - WriteInt32(w, o.(int32), n, err) + WriteByte(typeInt32, w, n, err) + WriteInt32(o.(int32), w, n, err) case uint32: - WriteByte(w, typeUInt32, n, err) - WriteUInt32(w, o.(uint32), n, err) + WriteByte(typeUInt32, w, n, err) + WriteUInt32(o.(uint32), w, n, err) case int64: - WriteByte(w, typeInt64, n, err) - WriteInt64(w, o.(int64), n, err) + WriteByte(typeInt64, w, n, err) + WriteInt64(o.(int64), w, n, err) case uint64: - WriteByte(w, typeUInt64, n, err) - WriteUInt64(w, o.(uint64), n, err) + WriteByte(typeUInt64, w, n, err) + WriteUInt64(o.(uint64), w, n, err) case int: - WriteByte(w, typeVarInt, n, err) - WriteVarInt(w, o.(int), n, err) + WriteByte(typeVarInt, w, n, err) + WriteVarInt(o.(int), w, n, err) case uint: - WriteByte(w, typeUVarInt, n, err) - WriteUVarInt(w, o.(uint), n, err) + WriteByte(typeUVarInt, w, n, err) + WriteUVarInt(o.(uint), w, n, err) case string: - WriteByte(w, typeString, n, err) - WriteString(w, o.(string), n, err) + WriteByte(typeString, w, n, err) + WriteString(o.(string), w, n, err) case []byte: - WriteByte(w, typeByteSlice, n, err) - WriteByteSlice(w, o.([]byte), n, err) + WriteByte(typeByteSlice, w, n, err) + WriteByteSlice(o.([]byte), w, n, err) case time.Time: - WriteByte(w, typeTime, n, err) - WriteTime(w, o.(time.Time), n, err) + WriteByte(typeTime, w, n, err) + WriteTime(o.(time.Time), w, n, err) default: panic("Unsupported type") } } -func (bc basicCodec) Decode(r io.Reader, n *int64, err *error) (o interface{}) { +func BasicCodecDecoder(r io.Reader, n *int64, err *error) (o interface{}) { type_ := ReadByte(r, n, err) switch type_ { case typeByte: @@ -121,7 +121,7 @@ func (bc basicCodec) Decode(r io.Reader, n *int64, err *error) (o interface{}) { return o } -func (bc basicCodec) Compare(o1 interface{}, o2 interface{}) int { +func BasicCodecComparator(o1 interface{}, o2 interface{}) int { switch o1.(type) { case byte: return int(o1.(byte) - o2.(byte)) @@ -154,3 +154,9 @@ func (bc basicCodec) Compare(o1 interface{}, o2 interface{}) int { panic("Unsupported type") } } + +var BasicCodec = Codec{ + Encode: BasicCodecEncoder, + Decode: BasicCodecDecoder, + Compare: BasicCodecComparator, +} diff --git a/binary/int.go b/binary/int.go index 42445e18..23372302 100644 --- a/binary/int.go +++ b/binary/int.go @@ -8,20 +8,20 @@ import ( // Byte -func WriteByte(w io.Writer, b byte, n *int64, err *error) { - WriteTo(w, []byte{b}, n, err) +func WriteByte(b byte, w io.Writer, n *int64, err *error) { + WriteTo([]byte{b}, w, n, err) } func ReadByte(r io.Reader, n *int64, err *error) byte { buf := make([]byte, 1) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return buf[0] } // Int8 -func WriteInt8(w io.Writer, i int8, n *int64, err *error) { - WriteByte(w, byte(i), n, err) +func WriteInt8(i int8, w io.Writer, n *int64, err *error) { + WriteByte(byte(i), w, n, err) } func ReadInt8(r io.Reader, n *int64, err *error) int8 { @@ -30,8 +30,8 @@ func ReadInt8(r io.Reader, n *int64, err *error) int8 { // UInt8 -func WriteUInt8(w io.Writer, i uint8, n *int64, err *error) { - WriteByte(w, byte(i), n, err) +func WriteUInt8(i uint8, w io.Writer, n *int64, err *error) { + WriteByte(byte(i), w, n, err) } func ReadUInt8(r io.Reader, n *int64, err *error) uint8 { @@ -40,40 +40,40 @@ func ReadUInt8(r io.Reader, n *int64, err *error) uint8 { // Int16 -func WriteInt16(w io.Writer, i int16, n *int64, err *error) { +func WriteInt16(i int16, w io.Writer, n *int64, err *error) { buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(i)) *n += 2 - WriteTo(w, buf, n, err) + WriteTo(buf, w, n, err) } func ReadInt16(r io.Reader, n *int64, err *error) int16 { buf := make([]byte, 2) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return int16(binary.LittleEndian.Uint16(buf)) } // UInt16 -func WriteUInt16(w io.Writer, i uint16, n *int64, err *error) { +func WriteUInt16(i uint16, w io.Writer, n *int64, err *error) { buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(i)) *n += 2 - WriteTo(w, buf, n, err) + WriteTo(buf, w, n, err) } func ReadUInt16(r io.Reader, n *int64, err *error) uint16 { buf := make([]byte, 2) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return uint16(binary.LittleEndian.Uint16(buf)) } // []UInt16 -func WriteUInt16s(w io.Writer, iz []uint16, n *int64, err *error) { - WriteUInt32(w, uint32(len(iz)), n, err) +func WriteUInt16s(iz []uint16, w io.Writer, n *int64, err *error) { + WriteUInt32(uint32(len(iz)), w, n, err) for _, i := range iz { - WriteUInt16(w, i, n, err) + WriteUInt16(i, w, n, err) if *err != nil { return } @@ -98,71 +98,71 @@ func ReadUInt16s(r io.Reader, n *int64, err *error) []uint16 { // Int32 -func WriteInt32(w io.Writer, i int32, n *int64, err *error) { +func WriteInt32(i int32, w io.Writer, n *int64, err *error) { buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, uint32(i)) *n += 4 - WriteTo(w, buf, n, err) + WriteTo(buf, w, n, err) } func ReadInt32(r io.Reader, n *int64, err *error) int32 { buf := make([]byte, 4) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return int32(binary.LittleEndian.Uint32(buf)) } // UInt32 -func WriteUInt32(w io.Writer, i uint32, n *int64, err *error) { +func WriteUInt32(i uint32, w io.Writer, n *int64, err *error) { buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, uint32(i)) *n += 4 - WriteTo(w, buf, n, err) + WriteTo(buf, w, n, err) } func ReadUInt32(r io.Reader, n *int64, err *error) uint32 { buf := make([]byte, 4) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return uint32(binary.LittleEndian.Uint32(buf)) } // Int64 -func WriteInt64(w io.Writer, i int64, n *int64, err *error) { +func WriteInt64(i int64, w io.Writer, n *int64, err *error) { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, uint64(i)) *n += 8 - WriteTo(w, buf, n, err) + WriteTo(buf, w, n, err) } func ReadInt64(r io.Reader, n *int64, err *error) int64 { buf := make([]byte, 8) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return int64(binary.LittleEndian.Uint64(buf)) } // UInt64 -func WriteUInt64(w io.Writer, i uint64, n *int64, err *error) { +func WriteUInt64(i uint64, w io.Writer, n *int64, err *error) { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, uint64(i)) *n += 8 - WriteTo(w, buf, n, err) + WriteTo(buf, w, n, err) } func ReadUInt64(r io.Reader, n *int64, err *error) uint64 { buf := make([]byte, 8) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return uint64(binary.LittleEndian.Uint64(buf)) } // VarInt -func WriteVarInt(w io.Writer, i int, n *int64, err *error) { +func WriteVarInt(i int, w io.Writer, n *int64, err *error) { buf := make([]byte, 9) n_ := int64(binary.PutVarint(buf, int64(i))) *n += n_ - WriteTo(w, buf[:n_], n, err) + WriteTo(buf[:n_], w, n, err) } func ReadVarInt(r io.Reader, n *int64, err *error) int { @@ -174,11 +174,11 @@ func ReadVarInt(r io.Reader, n *int64, err *error) int { // UVarInt -func WriteUVarInt(w io.Writer, i uint, n *int64, err *error) { +func WriteUVarInt(i uint, w io.Writer, n *int64, err *error) { buf := make([]byte, 9) n_ := int64(binary.PutUvarint(buf, uint64(i))) *n += n_ - WriteTo(w, buf[:n_], n, err) + WriteTo(buf[:n_], w, n, err) } func ReadUVarInt(r io.Reader, n *int64, err *error) uint { diff --git a/binary/reflect.go b/binary/reflect.go new file mode 100644 index 00000000..56981df3 --- /dev/null +++ b/binary/reflect.go @@ -0,0 +1,198 @@ +package binary + +import ( + "fmt" + "io" + "reflect" +) + +type TypeInfo struct { + Type reflect.Type // The type + Encoder Encoder // Optional custom encoder function + Decoder Decoder // Optional custom decoder function +} + +// If a type implements TypeByte, the byte is included +// as the first byte for encoding. This is used to encode +// interfaces/union types. In this case the decoding should +// be done manually with a switch statement, and so the +// reflection-based decoder provided here does not expect this +// prefix byte. +// See the reactor implementations for use-cases. +type HasTypeByte interface { + TypeByte() byte +} + +var typeInfos = map[reflect.Type]*TypeInfo{} + +func RegisterType(info *TypeInfo) bool { + + // Register the type info + typeInfos[info.Type] = info + + // Also register the underlying struct's info, if info.Type is a pointer. + if info.Type.Kind() == reflect.Ptr { + rt := info.Type.Elem() + typeInfos[rt] = info + } + + return true +} + +func readReflect(rv reflect.Value, rt reflect.Type, r io.Reader, n *int64, err *error) { + + // First, create a new struct if rv is nil pointer. + if rt.Kind() == reflect.Ptr && rv.IsNil() { + newRv := reflect.New(rt.Elem()) + rv.Set(newRv) + rv = newRv + } + + // Dereference pointer + // Still addressable, thus settable! + if rv.Kind() == reflect.Ptr { + rv, rt = rv.Elem(), rt.Elem() + } + + // Custom decoder + typeInfo := typeInfos[rt] + if typeInfo != nil && typeInfo.Decoder != nil { + decoded := typeInfo.Decoder(r, n, err) + decodedRv := reflect.Indirect(reflect.ValueOf(decoded)) + rv.Set(decodedRv) + return + } + + switch rt.Kind() { + case reflect.Slice: + elemRt := rt.Elem() + if elemRt.Kind() == reflect.Uint8 { + // Special case: Byteslices + byteslice := ReadByteSlice(r, n, err) + rv.Set(reflect.ValueOf(byteslice)) + } else { + // Read length + length := int(ReadUVarInt(r, n, err)) + sliceRv := reflect.MakeSlice(rt, length, length) + // Read elems + for i := 0; i < length; i++ { + elemRv := sliceRv.Index(i) + readReflect(elemRv, elemRt, r, n, err) + } + rv.Set(sliceRv) + } + + case reflect.Struct: + numFields := rt.NumField() + for i := 0; i < numFields; i++ { + field := rt.Field(i) + if field.Anonymous { + continue + } + fieldRv := rv.Field(i) + readReflect(fieldRv, field.Type, r, n, err) + } + + case reflect.String: + str := ReadString(r, n, err) + rv.SetString(str) + + case reflect.Uint64: + num := ReadUInt64(r, n, err) + rv.SetUint(uint64(num)) + + case reflect.Uint32: + num := ReadUInt32(r, n, err) + rv.SetUint(uint64(num)) + + case reflect.Uint16: + num := ReadUInt16(r, n, err) + rv.SetUint(uint64(num)) + + case reflect.Uint8: + num := ReadUInt8(r, n, err) + rv.SetUint(uint64(num)) + + case reflect.Uint: + num := ReadUVarInt(r, n, err) + rv.SetUint(uint64(num)) + + default: + panic(fmt.Sprintf("Unknown field type %v", rt.Kind())) + } +} + +func writeReflect(rv reflect.Value, rt reflect.Type, w io.Writer, n *int64, err *error) { + + // Custom encoder + typeInfo := typeInfos[rt] + if typeInfo != nil && typeInfo.Encoder != nil { + typeInfo.Encoder(rv.Interface(), w, n, err) + return + } + + // Dereference pointer or interface + if rt.Kind() == reflect.Ptr { + rt = rt.Elem() + rv = rv.Elem() + } else if rt.Kind() == reflect.Interface { + rv = rv.Elem() + rt = rv.Type() + } + + // Write TypeByte prefix + if rt.Implements(reflect.TypeOf((*HasTypeByte)(nil)).Elem()) { + WriteByte(rv.Interface().(HasTypeByte).TypeByte(), w, n, err) + } + + switch rt.Kind() { + case reflect.Slice: + elemRt := rt.Elem() + if elemRt.Kind() == reflect.Uint8 { + // Special case: Byteslices + byteslice := rv.Interface().([]byte) + WriteByteSlice(byteslice, w, n, err) + } else { + // Write length + length := rv.Len() + WriteUVarInt(uint(length), w, n, err) + // Write elems + for i := 0; i < length; i++ { + elemRv := rv.Index(i) + writeReflect(elemRv, elemRt, w, n, err) + } + } + + case reflect.Struct: + numFields := rt.NumField() + for i := 0; i < numFields; i++ { + field := rt.Field(i) + if field.Anonymous { + continue + } + fieldRv := rv.Field(i) + writeReflect(fieldRv, field.Type, w, n, err) + } + + case reflect.String: + WriteString(rv.String(), w, n, err) + + case reflect.Uint64: + WriteUInt64(rv.Uint(), w, n, err) + + case reflect.Uint32: + WriteUInt32(uint32(rv.Uint()), w, n, err) + + case reflect.Uint16: + WriteUInt16(uint16(rv.Uint()), w, n, err) + + case reflect.Uint8: + WriteUInt8(uint8(rv.Uint()), w, n, err) + + case reflect.Uint: + WriteUVarInt(uint(rv.Uint()), w, n, err) + + default: + panic(fmt.Sprintf("Unknown field type %v", rt.Kind())) + } +} diff --git a/binary/string.go b/binary/string.go index 3ad25d0d..6968ad44 100644 --- a/binary/string.go +++ b/binary/string.go @@ -4,9 +4,9 @@ import "io" // String -func WriteString(w io.Writer, s string, n *int64, err *error) { - WriteUInt32(w, uint32(len(s)), n, err) - WriteTo(w, []byte(s), n, err) +func WriteString(s string, w io.Writer, n *int64, err *error) { + WriteUInt32(uint32(len(s)), w, n, err) + WriteTo([]byte(s), w, n, err) } func ReadString(r io.Reader, n *int64, err *error) string { @@ -15,6 +15,6 @@ func ReadString(r io.Reader, n *int64, err *error) string { return "" } buf := make([]byte, int(length)) - ReadFull(r, buf, n, err) + ReadFull(buf, r, n, err) return string(buf) } diff --git a/binary/time.go b/binary/time.go index 8cc36f86..9c678046 100644 --- a/binary/time.go +++ b/binary/time.go @@ -7,8 +7,8 @@ import ( // Time -func WriteTime(w io.Writer, t time.Time, n *int64, err *error) { - WriteInt64(w, t.UnixNano(), n, err) +func WriteTime(t time.Time, w io.Writer, n *int64, err *error) { + WriteInt64(t.UnixNano(), w, n, err) } func ReadTime(r io.Reader, n *int64, err *error) time.Time { diff --git a/binary/util.go b/binary/util.go index e80c7a0b..e1b0c7d8 100644 --- a/binary/util.go +++ b/binary/util.go @@ -2,36 +2,46 @@ package binary import ( "bytes" + "code.google.com/p/go.crypto/ripemd160" "crypto/sha256" ) -func BinaryBytes(b Binary) []byte { - buf := bytes.NewBuffer(nil) - _, err := b.WriteTo(buf) - if err != nil { +func BinaryBytes(o interface{}) []byte { + w, n, err := new(bytes.Buffer), new(int64), new(error) + WriteBinary(o, w, n, err) + if *err != nil { panic(err) } - return buf.Bytes() + return w.Bytes() } // NOTE: does not care about the type, only the binary representation. -func BinaryEqual(a, b Binary) bool { +func BinaryEqual(a, b interface{}) bool { aBytes := BinaryBytes(a) bBytes := BinaryBytes(b) return bytes.Equal(aBytes, bBytes) } // NOTE: does not care about the type, only the binary representation. -func BinaryCompare(a, b Binary) int { +func BinaryCompare(a, b interface{}) int { aBytes := BinaryBytes(a) bBytes := BinaryBytes(b) return bytes.Compare(aBytes, bBytes) } -func BinaryHash(b Binary) []byte { - hasher := sha256.New() - _, err := b.WriteTo(hasher) - if err != nil { +func BinarySha256(o interface{}) []byte { + hasher, n, err := sha256.New(), new(int64), new(error) + WriteBinary(o, hasher, n, err) + if *err != nil { + panic(err) + } + return hasher.Sum(nil) +} + +func BinaryRipemd160(o interface{}) []byte { + hasher, n, err := ripemd160.New(), new(int64), new(error) + WriteBinary(o, hasher, n, err) + if *err != nil { panic(err) } return hasher.Sum(nil) diff --git a/blocks/block.go b/blocks/block.go index 253cecec..9591b86c 100644 --- a/blocks/block.go +++ b/blocks/block.go @@ -5,10 +5,10 @@ import ( "crypto/sha256" "errors" "fmt" - "io" "strings" "time" + . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" . "github.com/tendermint/tendermint/config" @@ -24,23 +24,8 @@ type Block struct { hash []byte } -func ReadBlock(r io.Reader, n *int64, err *error) *Block { - return &Block{ - Header: ReadHeader(r, n, err), - Validation: ReadValidation(r, n, err), - Data: ReadData(r, n, err), - } -} - -func (b *Block) WriteTo(w io.Writer) (n int64, err error) { - WriteBinary(w, b.Header, &n, &err) - WriteBinary(w, b.Validation, &n, &err) - WriteBinary(w, b.Data, &n, &err) - return -} - // Basic validation that doesn't involve state data. -func (b *Block) ValidateBasic(lastBlockHeight uint32, lastBlockHash []byte, +func (b *Block) ValidateBasic(lastBlockHeight uint, lastBlockHash []byte, lastBlockParts PartSetHeader, lastBlockTime time.Time) error { if b.Network != Config.Network { return errors.New("Invalid block network") @@ -115,7 +100,7 @@ func (b *Block) Description() string { type Header struct { Network string - Height uint32 + Height uint Time time.Time Fees uint64 LastBlockHash []byte @@ -126,37 +111,11 @@ type Header struct { hash []byte } -func ReadHeader(r io.Reader, n *int64, err *error) *Header { - if *err != nil { - return nil - } - return &Header{ - Network: ReadString(r, n, err), - Height: ReadUInt32(r, n, err), - Time: ReadTime(r, n, err), - Fees: ReadUInt64(r, n, err), - LastBlockHash: ReadByteSlice(r, n, err), - LastBlockParts: ReadPartSetHeader(r, n, err), - StateHash: ReadByteSlice(r, n, err), - } -} - -func (h *Header) WriteTo(w io.Writer) (n int64, err error) { - WriteString(w, h.Network, &n, &err) - WriteUInt32(w, h.Height, &n, &err) - WriteTime(w, h.Time, &n, &err) - WriteUInt64(w, h.Fees, &n, &err) - WriteByteSlice(w, h.LastBlockHash, &n, &err) - WriteBinary(w, h.LastBlockParts, &n, &err) - WriteByteSlice(w, h.StateHash, &n, &err) - return -} - func (h *Header) Hash() []byte { if h.hash == nil { - hasher := sha256.New() - _, err := h.WriteTo(hasher) - if err != nil { + hasher, n, err := sha256.New(), new(int64), new(error) + WriteBinary(h, hasher, n, err) + if *err != nil { panic(err) } h.hash = hasher.Sum(nil) @@ -186,30 +145,35 @@ func (h *Header) StringWithIndent(indent string) string { //----------------------------------------------------------------------------- +type Commit struct { + // It's not strictly needed here, but consider adding address here for convenience + Round uint + Signature SignatureEd25519 +} + +func (commit Commit) IsZero() bool { + return commit.Round == 0 && commit.Signature.IsZero() +} + +func (commit Commit) String() string { + return fmt.Sprintf("Commit{R:%v %X}", commit.Round, Fingerprint(commit.Signature.Bytes)) +} + +//------------------------------------- + type Validation struct { - Commits []RoundSignature + Commits []Commit // Volatile hash []byte bitArray BitArray } -func ReadValidation(r io.Reader, n *int64, err *error) *Validation { - return &Validation{ - Commits: ReadRoundSignatures(r, n, err), - } -} - -func (v *Validation) WriteTo(w io.Writer) (n int64, err error) { - WriteRoundSignatures(w, v.Commits, &n, &err) - return -} - func (v *Validation) Hash() []byte { if v.hash == nil { - bs := make([]Binary, len(v.Commits)) + bs := make([]interface{}, len(v.Commits)) for i, commit := range v.Commits { - bs[i] = Binary(commit) + bs[i] = commit } v.hash = merkle.HashFromBinaries(bs) } @@ -231,8 +195,8 @@ func (v *Validation) StringWithIndent(indent string) string { func (v *Validation) BitArray() BitArray { if v.bitArray.IsZero() { v.bitArray = NewBitArray(uint(len(v.Commits))) - for i, rsig := range v.Commits { - v.bitArray.SetIndex(uint(i), !rsig.IsZero()) + for i, commit := range v.Commits { + v.bitArray.SetIndex(uint(i), !commit.IsZero()) } } return v.bitArray @@ -247,28 +211,11 @@ type Data struct { hash []byte } -func ReadData(r io.Reader, n *int64, err *error) *Data { - numTxs := ReadUInt32(r, n, err) - txs := make([]Tx, 0, numTxs) - for i := uint32(0); i < numTxs; i++ { - txs = append(txs, ReadTx(r, n, err)) - } - return &Data{Txs: txs} -} - -func (data *Data) WriteTo(w io.Writer) (n int64, err error) { - WriteUInt32(w, uint32(len(data.Txs)), &n, &err) - for _, tx := range data.Txs { - WriteBinary(w, tx, &n, &err) - } - return -} - func (data *Data) Hash() []byte { if data.hash == nil { - bs := make([]Binary, len(data.Txs)) + bs := make([]interface{}, len(data.Txs)) for i, tx := range data.Txs { - bs[i] = Binary(tx) + bs[i] = tx } data.hash = merkle.HashFromBinaries(bs) } @@ -278,7 +225,7 @@ func (data *Data) Hash() []byte { func (data *Data) StringWithIndent(indent string) string { txStrings := make([]string, len(data.Txs)) for i, tx := range data.Txs { - txStrings[i] = tx.String() + txStrings[i] = fmt.Sprintf("Tx:%v", tx) } return fmt.Sprintf(`Data{ %s %v diff --git a/blocks/block_test.go b/blocks/block_test.go deleted file mode 100644 index 15723c87..00000000 --- a/blocks/block_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package blocks - -import ( - "bytes" - . "github.com/tendermint/tendermint/binary" - . "github.com/tendermint/tendermint/common" - "testing" -) - -func randSig() Signature { - return Signature{RandUInt64Exp(), RandBytes(32)} -} - -func randRoundSig() RoundSignature { - return RoundSignature{RandUInt16(), randSig()} -} - -func randBaseTx() BaseTx { - return BaseTx{0, RandUInt64Exp(), randSig()} -} - -func randBlock() *Block { - // Account Txs - sendTx := &SendTx{ - BaseTx: randBaseTx(), - To: RandUInt64Exp(), - Amount: RandUInt64Exp(), - } - nameTx := &NameTx{ - BaseTx: randBaseTx(), - Name: string(RandBytes(12)), - PubKey: RandBytes(32), - } - - // Validation Txs - bondTx := &BondTx{ - BaseTx: randBaseTx(), - //UnbondTo: RandUInt64Exp(), - } - unbondTx := &UnbondTx{ - BaseTx: randBaseTx(), - } - dupeoutTx := &DupeoutTx{ - VoteA: Vote{ - Height: RandUInt32Exp(), - Round: RandUInt16Exp(), - Type: VoteTypePrevote, - BlockHash: RandBytes(32), - Signature: randSig(), - }, - VoteB: Vote{ - Height: RandUInt32Exp(), - Round: RandUInt16Exp(), - Type: VoteTypePrevote, - BlockHash: RandBytes(32), - Signature: randSig(), - }, - } - - // Block - block := &Block{ - Header: &Header{ - Network: "Tendermint", - Height: RandUInt32Exp(), - Fees: RandUInt64Exp(), - Time: RandTime(), - LastBlockHash: RandBytes(32), - StateHash: RandBytes(32), - }, - Validation: &Validation{ - Commits: []RoundSignature{randRoundSig(), randRoundSig()}, - }, - Data: &Data{ - Txs: []Tx{sendTx, nameTx, bondTx, unbondTx, dupeoutTx}, - }, - } - return block -} - -func TestBlock(t *testing.T) { - - block := randBlock() - // Mutate the block and ensure that the hash changed. - lastHash := block.Hash() - expectChange := func(mutateFn func(b *Block), message string) { - // mutate block - mutateFn(block) - // nuke hashes - block.hash = nil - block.Header.hash = nil - block.Validation.hash = nil - block.Data.hash = nil - // compare - if bytes.Equal(lastHash, block.Hash()) { - t.Error(message) - } else { - lastHash = block.Hash() - } - } - expectChange(func(b *Block) { b.Header.Network = "blah" }, "Expected hash to depend on Network") - expectChange(func(b *Block) { b.Header.Height += 1 }, "Expected hash to depend on Height") - expectChange(func(b *Block) { b.Header.Fees += 1 }, "Expected hash to depend on Fees") - expectChange(func(b *Block) { b.Header.Time = RandTime() }, "Expected hash to depend on Time") - expectChange(func(b *Block) { b.Header.LastBlockHash = RandBytes(32) }, "Expected hash to depend on LastBlockHash") - expectChange(func(b *Block) { b.Header.StateHash = RandBytes(32) }, "Expected hash to depend on StateHash") - expectChange(func(b *Block) { b.Validation.Commits[0].Round += 1 }, "Expected hash to depend on Validation Commit") - expectChange(func(b *Block) { b.Validation.Commits[0].SignerId += 1 }, "Expected hash to depend on Validation Commit") - expectChange(func(b *Block) { b.Validation.Commits[0].Bytes = RandBytes(32) }, "Expected hash to depend on Validation Commit") - expectChange(func(b *Block) { b.Data.Txs[0].(*SendTx).Signature.SignerId += 1 }, "Expected hash to depend on tx Signature") - expectChange(func(b *Block) { b.Data.Txs[0].(*SendTx).Amount += 1 }, "Expected hash to depend on send tx Amount") - - // Write the block, read it in again, check hash. - block1 := randBlock() - block1Bytes := BinaryBytes(block1) - var n int64 - var err error - block2 := ReadBlock(bytes.NewReader(block1Bytes), &n, &err) - if err != nil { - t.Errorf("Reading block failed: %v", err) - } - if !bytes.Equal(block1.Hash(), block2.Hash()) { - t.Errorf("Expected write/read to preserve original hash") - t.Logf("\nBlock1:\n%v", block1) - t.Logf("\nBlock2:\n%v", block2) - } -} diff --git a/blocks/codec_test.go b/blocks/codec_test.go deleted file mode 100644 index 32c10769..00000000 --- a/blocks/codec_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package blocks - -import ( - "bytes" - "encoding/gob" - "encoding/json" - "testing" - "time" - - "github.com/ugorji/go/codec" - "github.com/vmihailenco/msgpack" -) - -func BenchmarkTestCustom(b *testing.B) { - b.StopTimer() - - h := &Header{ - Network: "Header", - Height: 123, - Fees: 123, - Time: time.Unix(123, 0), - LastBlockHash: []byte("prevhash"), - StateHash: []byte("statehash"), - } - - buf := bytes.NewBuffer(nil) - - b.StartTimer() - for i := 0; i < b.N; i++ { - buf.Reset() - h.WriteTo(buf) - var n int64 - var err error - h2 := ReadHeader(buf, &n, &err) - if h2.Network != "Header" { - b.Fatalf("wrong name") - } - } -} - -type HHeader struct { - Network string `json:"N"` - Height uint64 `json:"H"` - Fees uint64 `json:"F"` - Time uint64 `json:"T"` - LastBlockHash []byte `json:"PH"` - StateHash []byte `json:"SH"` -} - -func BenchmarkTestJSON(b *testing.B) { - b.StopTimer() - - h := &HHeader{ - Network: "Header", - Height: 123, - Fees: 123, - Time: 123, - LastBlockHash: []byte("prevhash"), - StateHash: []byte("statehash"), - } - h2 := &HHeader{} - - buf := bytes.NewBuffer(nil) - enc := json.NewEncoder(buf) - dec := json.NewDecoder(buf) - - b.StartTimer() - for i := 0; i < b.N; i++ { - buf.Reset() - enc.Encode(h) - dec.Decode(h2) - if h2.Network != "Header" { - b.Fatalf("wrong name") - } - } -} - -func BenchmarkTestGob(b *testing.B) { - b.StopTimer() - - h := &Header{ - Network: "Header", - Height: 123, - Fees: 123, - Time: time.Unix(123, 0), - LastBlockHash: []byte("prevhash"), - StateHash: []byte("statehash"), - } - h2 := &Header{} - - buf := bytes.NewBuffer(nil) - enc := gob.NewEncoder(buf) - dec := gob.NewDecoder(buf) - - b.StartTimer() - for i := 0; i < b.N; i++ { - buf.Reset() - enc.Encode(h) - dec.Decode(h2) - if h2.Network != "Header" { - b.Fatalf("wrong name") - } - } -} - -func BenchmarkTestMsgPack(b *testing.B) { - b.StopTimer() - - h := &Header{ - Network: "Header", - Height: 123, - Fees: 123, - Time: time.Unix(123, 0), - LastBlockHash: []byte("prevhash"), - StateHash: []byte("statehash"), - } - h2 := &Header{} - - buf := bytes.NewBuffer(nil) - enc := msgpack.NewEncoder(buf) - dec := msgpack.NewDecoder(buf) - - b.StartTimer() - for i := 0; i < b.N; i++ { - buf.Reset() - enc.Encode(h) - dec.Decode(h2) - if h2.Network != "Header" { - b.Fatalf("wrong name") - } - } -} - -func BenchmarkTestMsgPack2(b *testing.B) { - b.StopTimer() - - h := &Header{ - Network: "Header", - Height: 123, - Fees: 123, - Time: time.Unix(123, 0), - LastBlockHash: []byte("prevhash"), - StateHash: []byte("statehash"), - } - h2 := &Header{} - var mh codec.MsgpackHandle - handle := &mh - - buf := bytes.NewBuffer(nil) - enc := codec.NewEncoder(buf, handle) - dec := codec.NewDecoder(buf, handle) - - b.StartTimer() - for i := 0; i < b.N; i++ { - buf.Reset() - enc.Encode(h) - dec.Decode(h2) - if h2.Network != "Header" { - b.Fatalf("wrong name") - } - } -} diff --git a/blocks/part_set.go b/blocks/part_set.go index 7254a38f..a43ec0c3 100644 --- a/blocks/part_set.go +++ b/blocks/part_set.go @@ -9,7 +9,6 @@ import ( "strings" "sync" - . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/merkle" ) @@ -24,7 +23,7 @@ var ( ) type Part struct { - Index uint16 + Index uint Trail [][]byte Bytes []byte @@ -32,21 +31,6 @@ type Part struct { hash []byte } -func ReadPart(r io.Reader, n *int64, err *error) *Part { - return &Part{ - Index: ReadUInt16(r, n, err), - Trail: ReadByteSlices(r, n, err), - Bytes: ReadByteSlice(r, n, err), - } -} - -func (part *Part) WriteTo(w io.Writer) (n int64, err error) { - WriteUInt16(w, part.Index, &n, &err) - WriteByteSlices(w, part.Trail, &n, &err) - WriteByteSlice(w, part.Bytes, &n, &err) - return -} - func (part *Part) Hash() []byte { if part.hash != nil { return part.hash @@ -84,23 +68,10 @@ func (part *Part) StringWithIndent(indent string) string { //------------------------------------- type PartSetHeader struct { - Total uint16 + Total uint Hash []byte } -func ReadPartSetHeader(r io.Reader, n *int64, err *error) PartSetHeader { - return PartSetHeader{ - Total: ReadUInt16(r, n, err), - Hash: ReadByteSlice(r, n, err), - } -} - -func (psh PartSetHeader) WriteTo(w io.Writer) (n int64, err error) { - WriteUInt16(w, psh.Total, &n, &err) - WriteByteSlice(w, psh.Hash, &n, &err) - return -} - func (psh PartSetHeader) String() string { return fmt.Sprintf("PartSet{T:%v %X}", psh.Total, Fingerprint(psh.Hash)) } @@ -116,13 +87,13 @@ func (psh PartSetHeader) Equals(other PartSetHeader) bool { //------------------------------------- type PartSet struct { - total uint16 + total uint hash []byte mtx sync.Mutex parts []*Part partsBitArray BitArray - count uint16 + count uint } // Returns an immutable, full PartSet. @@ -135,7 +106,7 @@ func NewPartSetFromData(data []byte) *PartSet { partsBitArray := NewBitArray(uint(total)) for i := 0; i < total; i++ { part := &Part{ - Index: uint16(i), + Index: uint(i), Bytes: data[i*partSize : MinInt(len(data), (i+1)*partSize)], } parts[i] = part @@ -148,11 +119,11 @@ func NewPartSetFromData(data []byte) *PartSet { parts[i].Trail = trails[i].Flatten() } return &PartSet{ - total: uint16(total), + total: uint(total), hash: rootTrail.Hash, parts: parts, partsBitArray: partsBitArray, - count: uint16(total), + count: uint(total), } } @@ -207,14 +178,14 @@ func (ps *PartSet) HashesTo(hash []byte) bool { return bytes.Equal(ps.hash, hash) } -func (ps *PartSet) Count() uint16 { +func (ps *PartSet) Count() uint { if ps == nil { return 0 } return ps.count } -func (ps *PartSet) Total() uint16 { +func (ps *PartSet) Total() uint { if ps == nil { return 0 } @@ -247,7 +218,7 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) { return true, nil } -func (ps *PartSet) GetPart(index uint16) *Part { +func (ps *PartSet) GetPart(index uint) *Part { ps.mtx.Lock() defer ps.mtx.Unlock() return ps.parts[index] diff --git a/blocks/part_set_test.go b/blocks/part_set_test.go index 60e929f7..22c7e219 100644 --- a/blocks/part_set_test.go +++ b/blocks/part_set_test.go @@ -27,7 +27,7 @@ func TestBasicPartSet(t *testing.T) { // Test adding parts to a new partSet. partSet2 := NewPartSetFromHeader(partSet.Header()) - for i := uint16(0); i < partSet.Total(); i++ { + for i := uint(0); i < partSet.Total(); i++ { part := partSet.GetPart(i) //t.Logf("\n%v", part) added, err := partSet2.AddPart(part) diff --git a/blocks/signature.go b/blocks/signature.go deleted file mode 100644 index 6f90ffd4..00000000 --- a/blocks/signature.go +++ /dev/null @@ -1,111 +0,0 @@ -package blocks - -import ( - "fmt" - "io" - - . "github.com/tendermint/tendermint/binary" - . "github.com/tendermint/tendermint/common" -) - -type Signable interface { - Binary - GetSignature() Signature - SetSignature(Signature) -} - -//----------------------------------------------------------------------------- - -type Signature struct { - SignerId uint64 - Bytes []byte -} - -func ReadSignature(r io.Reader, n *int64, err *error) Signature { - return Signature{ - SignerId: ReadUInt64(r, n, err), - Bytes: ReadByteSlice(r, n, err), - } -} - -func (sig Signature) IsZero() bool { - return len(sig.Bytes) == 0 -} - -func (sig Signature) WriteTo(w io.Writer) (n int64, err error) { - WriteUInt64(w, sig.SignerId, &n, &err) - WriteByteSlice(w, sig.Bytes, &n, &err) - return -} - -func (sig Signature) String() string { - return fmt.Sprintf("Signature{Id:%v %X}", sig.SignerId, Fingerprint(sig.Bytes)) -} - -//------------------------------------- - -func ReadSignatures(r io.Reader, n *int64, err *error) (sigs []Signature) { - length := ReadUInt32(r, n, err) - for i := uint32(0); i < length; i++ { - sigs = append(sigs, ReadSignature(r, n, err)) - } - return -} - -func WriteSignatures(w io.Writer, sigs []Signature, n *int64, err *error) { - WriteUInt32(w, uint32(len(sigs)), n, err) - for _, sig := range sigs { - WriteBinary(w, sig, n, err) - if *err != nil { - return - } - } -} - -//----------------------------------------------------------------------------- - -type RoundSignature struct { - Round uint16 - Signature -} - -func ReadRoundSignature(r io.Reader, n *int64, err *error) RoundSignature { - return RoundSignature{ - ReadUInt16(r, n, err), - ReadSignature(r, n, err), - } -} - -func (rsig RoundSignature) WriteTo(w io.Writer) (n int64, err error) { - WriteUInt16(w, rsig.Round, &n, &err) - WriteBinary(w, rsig.Signature, &n, &err) - return -} - -func (rsig RoundSignature) IsZero() bool { - return rsig.Round == 0 && rsig.SignerId == 0 && len(rsig.Bytes) == 0 -} - -func (rsig RoundSignature) String() string { - return fmt.Sprintf("RoundSignature{R:%v Id:%v %X}", rsig.Round, rsig.SignerId, Fingerprint(rsig.Bytes)) -} - -//------------------------------------- - -func ReadRoundSignatures(r io.Reader, n *int64, err *error) (rsigs []RoundSignature) { - length := ReadUInt32(r, n, err) - for i := uint32(0); i < length; i++ { - rsigs = append(rsigs, ReadRoundSignature(r, n, err)) - } - return -} - -func WriteRoundSignatures(w io.Writer, rsigs []RoundSignature, n *int64, err *error) { - WriteUInt32(w, uint32(len(rsigs)), n, err) - for _, rsig := range rsigs { - WriteBinary(w, rsig, n, err) - if *err != nil { - return - } - } -} diff --git a/blocks/store.go b/blocks/store.go index 67bba864..53aadcf0 100644 --- a/blocks/store.go +++ b/blocks/store.go @@ -2,8 +2,8 @@ package blocks import ( "bytes" - "encoding/binary" "encoding/json" + "fmt" "io" . "github.com/tendermint/tendermint/binary" @@ -17,7 +17,7 @@ import ( Simple low level store for blocks, which is actually stored as separte parts (wire format). */ type BlockStore struct { - height uint32 + height uint db db_.DB } @@ -30,7 +30,7 @@ func NewBlockStore(db db_.DB) *BlockStore { } // Height() returns the last known contiguous block height. -func (bs *BlockStore) Height() uint32 { +func (bs *BlockStore) Height() uint { return bs.height } @@ -42,49 +42,49 @@ func (bs *BlockStore) GetReader(key []byte) io.Reader { return bytes.NewReader(bytez) } -func (bs *BlockStore) LoadBlock(height uint32) *Block { +func (bs *BlockStore) LoadBlock(height uint) *Block { var n int64 var err error - meta := ReadBlockMeta(bs.GetReader(calcBlockMetaKey(height)), &n, &err) + meta := ReadBinary(&BlockMeta{}, bs.GetReader(calcBlockMetaKey(height)), &n, &err).(*BlockMeta) if err != nil { Panicf("Error reading block meta: %v", err) } bytez := []byte{} - for i := uint16(0); i < meta.Parts.Total; i++ { + for i := uint(0); i < meta.Parts.Total; i++ { part := bs.LoadBlockPart(height, i) bytez = append(bytez, part.Bytes...) } - block := ReadBlock(bytes.NewReader(bytez), &n, &err) + block := ReadBinary(&Block{}, bytes.NewReader(bytez), &n, &err).(*Block) if err != nil { Panicf("Error reading block: %v", err) } return block } -func (bs *BlockStore) LoadBlockPart(height uint32, index uint16) *Part { +func (bs *BlockStore) LoadBlockPart(height uint, index uint) *Part { var n int64 var err error - part := ReadPart(bs.GetReader(calcBlockPartKey(height, index)), &n, &err) + part := ReadBinary(&Part{}, bs.GetReader(calcBlockPartKey(height, index)), &n, &err).(*Part) if err != nil { Panicf("Error reading block part: %v", err) } return part } -func (bs *BlockStore) LoadBlockMeta(height uint32) *BlockMeta { +func (bs *BlockStore) LoadBlockMeta(height uint) *BlockMeta { var n int64 var err error - meta := ReadBlockMeta(bs.GetReader(calcBlockMetaKey(height)), &n, &err) + meta := ReadBinary(&BlockMeta{}, bs.GetReader(calcBlockMetaKey(height)), &n, &err).(*BlockMeta) if err != nil { Panicf("Error reading block meta: %v", err) } return meta } -func (bs *BlockStore) LoadBlockValidation(height uint32) *Validation { +func (bs *BlockStore) LoadBlockValidation(height uint) *Validation { var n int64 var err error - validation := ReadValidation(bs.GetReader(calcBlockValidationKey(height)), &n, &err) + validation := ReadBinary(&Validation{}, bs.GetReader(calcBlockValidationKey(height)), &n, &err).(*Validation) if err != nil { Panicf("Error reading validation: %v", err) } @@ -108,7 +108,7 @@ func (bs *BlockStore) SaveBlock(block *Block, blockParts *PartSet) { metaBytes := BinaryBytes(meta) bs.db.Set(calcBlockMetaKey(height), metaBytes) // Save block parts - for i := uint16(0); i < blockParts.Total(); i++ { + for i := uint(0); i < blockParts.Total(); i++ { bs.saveBlockPart(height, i, blockParts.GetPart(i)) } // Save block validation (duplicate and separate) @@ -120,7 +120,7 @@ func (bs *BlockStore) SaveBlock(block *Block, blockParts *PartSet) { bs.height = height } -func (bs *BlockStore) saveBlockPart(height uint32, index uint16, part *Part) { +func (bs *BlockStore) saveBlockPart(height uint, index uint, part *Part) { if height != bs.height+1 { Panicf("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.height+1, height) } @@ -136,40 +136,18 @@ type BlockMeta struct { Header *Header // The block's Header } -func ReadBlockMeta(r io.Reader, n *int64, err *error) *BlockMeta { - return &BlockMeta{ - Hash: ReadByteSlice(r, n, err), - Parts: ReadPartSetHeader(r, n, err), - Header: ReadHeader(r, n, err), - } -} - -func (bm *BlockMeta) WriteTo(w io.Writer) (n int64, err error) { - WriteByteSlice(w, bm.Hash, &n, &err) - WriteBinary(w, bm.Parts, &n, &err) - WriteBinary(w, bm.Header, &n, &err) - return -} - //----------------------------------------------------------------------------- -func calcBlockMetaKey(height uint32) []byte { - buf := [5]byte{'H'} - binary.BigEndian.PutUint32(buf[1:5], height) - return buf[:] +func calcBlockMetaKey(height uint) []byte { + return []byte(fmt.Sprintf("H:%v", height)) } -func calcBlockPartKey(height uint32, partIndex uint16) []byte { - buf := [7]byte{'P'} - binary.BigEndian.PutUint32(buf[1:5], height) - binary.BigEndian.PutUint16(buf[5:7], partIndex) - return buf[:] +func calcBlockPartKey(height uint, partIndex uint) []byte { + return []byte(fmt.Sprintf("P:%v:%v", height, partIndex)) } -func calcBlockValidationKey(height uint32) []byte { - buf := [5]byte{'V'} - binary.BigEndian.PutUint32(buf[1:5], height) - return buf[:] +func calcBlockValidationKey(height uint) []byte { + return []byte(fmt.Sprintf("V:%v", height)) } //----------------------------------------------------------------------------- @@ -177,7 +155,7 @@ func calcBlockValidationKey(height uint32) []byte { var blockStoreKey = []byte("blockStore") type BlockStoreJSON struct { - Height uint32 + Height uint } func (bsj BlockStoreJSON) Save(db db_.DB) { diff --git a/blocks/tx.go b/blocks/tx.go index 96063e8e..edbdd8ff 100644 --- a/blocks/tx.go +++ b/blocks/tx.go @@ -1,35 +1,33 @@ package blocks import ( - "fmt" + "errors" "io" + "reflect" + . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" ) /* +Tx (Transaction) is an atomic operation on the ledger state. + Account Txs: -1. Send Send coins to account -2. Name Associate account with a name +1. SendTx Send coins to address Validation Txs: -3. Bond New validator posts a bond -4. Unbond Validator leaves -5. Dupeout Validator dupes out (signs twice) +1. BondTx New validator posts a bond +2. UnbondTx Validator leaves +3. DupeoutTx Validator dupes out (equivocates) */ - type Tx interface { - Signable - GetSequence() uint - GetFee() uint64 - String() string + WriteSignBytes(w io.Writer, n *int64, err *error) } const ( // Account transactions TxTypeSend = byte(0x01) - TxTypeName = byte(0x02) // Validation transactions TxTypeBond = byte(0x11) @@ -37,174 +35,150 @@ const ( TxTypeDupeout = byte(0x13) ) -func ReadTx(r io.Reader, n *int64, err *error) Tx { +var ( + ErrTxInvalidAddress = errors.New("Error invalid address") + ErrTxDuplicateAddress = errors.New("Error duplicate address") + ErrTxInvalidAmount = errors.New("Error invalid amount") + ErrTxInsufficientFunds = errors.New("Error insufficient funds") + ErrTxInvalidSignature = errors.New("Error invalid signature") + ErrTxInvalidSequence = errors.New("Error invalid sequence") +) + +func TxDecoder(r io.Reader, n *int64, err *error) interface{} { switch t := ReadByte(r, n, err); t { case TxTypeSend: - return &SendTx{ - BaseTx: ReadBaseTx(r, n, err), - To: ReadUInt64(r, n, err), - Amount: ReadUInt64(r, n, err), - } - case TxTypeName: - return &NameTx{ - BaseTx: ReadBaseTx(r, n, err), - Name: ReadString(r, n, err), - PubKey: ReadByteSlice(r, n, err), - } + return ReadBinary(&SendTx{}, r, n, err) case TxTypeBond: - return &BondTx{ - BaseTx: ReadBaseTx(r, n, err), - //UnbondTo: ReadUInt64(r, n, err), - } + return ReadBinary(&BondTx{}, r, n, err) case TxTypeUnbond: - return &UnbondTx{ - BaseTx: ReadBaseTx(r, n, err), - } + return ReadBinary(&UnbondTx{}, r, n, err) case TxTypeDupeout: - return &DupeoutTx{ - BaseTx: ReadBaseTx(r, n, err), - VoteA: *ReadVote(r, n, err), - VoteB: *ReadVote(r, n, err), - } + return ReadBinary(&DupeoutTx{}, r, n, err) default: *err = Errorf("Unknown Tx type %X", t) return nil } } +var _ = RegisterType(&TypeInfo{ + Type: reflect.TypeOf((*Tx)(nil)).Elem(), + Decoder: TxDecoder, +}) + //----------------------------------------------------------------------------- -type BaseTx struct { - Sequence uint - Fee uint64 - Signature Signature +type TxInput struct { + Address []byte // Hash of the PubKey + Amount uint64 // Must not exceed account balance + Sequence uint // Must be 1 greater than the last committed TxInput + Signature Signature // Depends on the PubKey type and the whole Tx } -func ReadBaseTx(r io.Reader, n *int64, err *error) BaseTx { - return BaseTx{ - Sequence: ReadUVarInt(r, n, err), - Fee: ReadUInt64(r, n, err), - Signature: ReadSignature(r, n, err), +func (txIn *TxInput) ValidateBasic() error { + if len(txIn.Address) != 20 { + return ErrTxInvalidAddress } + if txIn.Amount == 0 { + return ErrTxInvalidAmount + } + return nil } -func (tx BaseTx) WriteTo(w io.Writer) (n int64, err error) { - WriteUVarInt(w, tx.Sequence, &n, &err) - WriteUInt64(w, tx.Fee, &n, &err) - WriteBinary(w, tx.Signature, &n, &err) - return +func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int64, err *error) { + WriteByteSlice(txIn.Address, w, n, err) + WriteUInt64(txIn.Amount, w, n, err) + WriteUVarInt(txIn.Sequence, w, n, err) } -func (tx *BaseTx) GetSequence() uint { - return tx.Sequence +//----------------------------------------------------------------------------- + +type TxOutput struct { + Address []byte // Hash of the PubKey + Amount uint64 // The sum of all outputs must not exceed the inputs. } -func (tx *BaseTx) GetSignature() Signature { - return tx.Signature +func (txOut *TxOutput) ValidateBasic() error { + if len(txOut.Address) != 20 { + return ErrTxInvalidAddress + } + if txOut.Amount == 0 { + return ErrTxInvalidAmount + } + return nil } -func (tx *BaseTx) GetFee() uint64 { - return tx.Fee -} - -func (tx *BaseTx) SetSignature(sig Signature) { - tx.Signature = sig -} - -func (tx *BaseTx) String() string { - return fmt.Sprintf("{S:%v F:%v Sig:%X}", tx.Sequence, tx.Fee, tx.Signature) +func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int64, err *error) { + WriteByteSlice(txOut.Address, w, n, err) + WriteUInt64(txOut.Amount, w, n, err) } //----------------------------------------------------------------------------- type SendTx struct { - BaseTx - To uint64 - Amount uint64 + Inputs []*TxInput + Outputs []*TxOutput } -func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, TxTypeSend, &n, &err) - WriteBinary(w, tx.BaseTx, &n, &err) - WriteUInt64(w, tx.To, &n, &err) - WriteUInt64(w, tx.Amount, &n, &err) - return -} +func (tx *SendTx) TypeByte() byte { return TxTypeSend } -func (tx *SendTx) String() string { - return fmt.Sprintf("SendTx{%v To:%v Amount:%v}", tx.BaseTx, tx.To, tx.Amount) -} - -//----------------------------------------------------------------------------- - -type NameTx struct { - BaseTx - Name string - PubKey []byte -} - -func (tx *NameTx) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, TxTypeName, &n, &err) - WriteBinary(w, tx.BaseTx, &n, &err) - WriteString(w, tx.Name, &n, &err) - WriteByteSlice(w, tx.PubKey, &n, &err) - return -} - -func (tx *NameTx) String() string { - return fmt.Sprintf("NameTx{%v Name:%v PubKey:%X}", tx.BaseTx, tx.Name, tx.PubKey) +func (tx *SendTx) WriteSignBytes(w io.Writer, n *int64, err *error) { + WriteUVarInt(uint(len(tx.Inputs)), w, n, err) + for _, in := range tx.Inputs { + in.WriteSignBytes(w, n, err) + } + WriteUVarInt(uint(len(tx.Outputs)), w, n, err) + for _, out := range tx.Outputs { + out.WriteSignBytes(w, n, err) + } } //----------------------------------------------------------------------------- type BondTx struct { - BaseTx - //UnbondTo uint64 + PubKey PubKeyEd25519 + Inputs []*TxInput + UnbondTo []*TxOutput } -func (tx *BondTx) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, TxTypeBond, &n, &err) - WriteBinary(w, tx.BaseTx, &n, &err) - //WriteUInt64(w, tx.UnbondTo, &n, &err) - return -} +func (tx *BondTx) TypeByte() byte { return TxTypeBond } -func (tx *BondTx) String() string { - return fmt.Sprintf("BondTx{%v}", tx.BaseTx) +func (tx *BondTx) WriteSignBytes(w io.Writer, n *int64, err *error) { + WriteBinary(tx.PubKey, w, n, err) + WriteUVarInt(uint(len(tx.Inputs)), w, n, err) + for _, in := range tx.Inputs { + in.WriteSignBytes(w, n, err) + } + WriteUVarInt(uint(len(tx.UnbondTo)), w, n, err) + for _, out := range tx.UnbondTo { + out.WriteSignBytes(w, n, err) + } } //----------------------------------------------------------------------------- type UnbondTx struct { - BaseTx + Address []byte + Height uint + Signature SignatureEd25519 } -func (tx *UnbondTx) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, TxTypeUnbond, &n, &err) - WriteBinary(w, tx.BaseTx, &n, &err) - return -} +func (tx *UnbondTx) TypeByte() byte { return TxTypeUnbond } -func (tx *UnbondTx) String() string { - return fmt.Sprintf("UnbondTx{%v}", tx.BaseTx) +func (tx *UnbondTx) WriteSignBytes(w io.Writer, n *int64, err *error) { + WriteByteSlice(tx.Address, w, n, err) + WriteUVarInt(tx.Height, w, n, err) } //----------------------------------------------------------------------------- type DupeoutTx struct { - BaseTx - VoteA Vote - VoteB Vote + Address []byte + VoteA Vote + VoteB Vote } -func (tx *DupeoutTx) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, TxTypeDupeout, &n, &err) - WriteBinary(w, tx.BaseTx, &n, &err) - WriteBinary(w, &tx.VoteA, &n, &err) - WriteBinary(w, &tx.VoteB, &n, &err) - return -} +func (tx *DupeoutTx) TypeByte() byte { return TxTypeDupeout } -func (tx *DupeoutTx) String() string { - return fmt.Sprintf("DupeoutTx{%v VoteA:%v VoteB:%v}", tx.BaseTx, tx.VoteA, tx.VoteB) +func (tx *DupeoutTx) WriteSignBytes(w io.Writer, n *int64, err *error) { + panic("DupeoutTx has no sign bytes") } diff --git a/blocks/vote.go b/blocks/vote.go index c44ffd27..c4f892aa 100644 --- a/blocks/vote.go +++ b/blocks/vote.go @@ -5,6 +5,7 @@ import ( "fmt" "io" + . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" ) @@ -23,53 +24,34 @@ var ( ErrVoteConflictingSignature = errors.New("Conflicting round vote signature") ) -// Represents a prevote, precommit, or commit vote for proposals. +// Represents a prevote, precommit, or commit vote for proposals from validators. +// Commit votes get aggregated into the next block's Validaiton. +// See the whitepaper for details. type Vote struct { - Height uint32 - Round uint16 + Height uint + Round uint Type byte BlockHash []byte // empty if vote is nil. BlockParts PartSetHeader // zero if vote is nil. - Signature + Signature SignatureEd25519 } -func ReadVote(r io.Reader, n *int64, err *error) *Vote { - return &Vote{ - Height: ReadUInt32(r, n, err), - Round: ReadUInt16(r, n, err), - Type: ReadByte(r, n, err), - BlockHash: ReadByteSlice(r, n, err), - BlockParts: ReadPartSetHeader(r, n, err), - Signature: ReadSignature(r, n, err), - } +func (vote *Vote) WriteSignBytes(w io.Writer, n *int64, err *error) { + WriteUVarInt(vote.Height, w, n, err) + WriteUVarInt(vote.Round, w, n, err) + WriteByte(vote.Type, w, n, err) + WriteByteSlice(vote.BlockHash, w, n, err) + WriteBinary(vote.BlockParts, w, n, err) } -func (v *Vote) WriteTo(w io.Writer) (n int64, err error) { - WriteUInt32(w, v.Height, &n, &err) - WriteUInt16(w, v.Round, &n, &err) - WriteByte(w, v.Type, &n, &err) - WriteByteSlice(w, v.BlockHash, &n, &err) - WriteBinary(w, v.BlockParts, &n, &err) - WriteBinary(w, v.Signature, &n, &err) - return +func (vote *Vote) Copy() *Vote { + voteCopy := *vote + return &voteCopy } -func (v *Vote) GetSignature() Signature { - return v.Signature -} - -func (v *Vote) SetSignature(sig Signature) { - v.Signature = sig -} - -func (v *Vote) Copy() *Vote { - vCopy := *v - return &vCopy -} - -func (v *Vote) String() string { +func (vote *Vote) String() string { var typeString string - switch v.Type { + switch vote.Type { case VoteTypePrevote: typeString = "Prevote" case VoteTypePrecommit: @@ -80,5 +62,5 @@ func (v *Vote) String() string { panic("Unknown vote type") } - return fmt.Sprintf("%v{%v/%v %X#%v %v}", typeString, v.Height, v.Round, Fingerprint(v.BlockHash), v.BlockParts, v.Signature) + return fmt.Sprintf("%v{%v/%v %X#%v %v}", typeString, vote.Height, vote.Round, Fingerprint(vote.BlockHash), vote.BlockParts, vote.Signature) } diff --git a/cmd/daemon.go b/cmd/daemon.go index 6e9022fc..dce8c47a 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -33,20 +33,18 @@ func NewNode() *Node { stateDB := db_.NewMemDB() // TODO configurable db. state := state_.LoadState(stateDB) if state == nil { - state = state_.GenesisStateFromFile(stateDB, config.RootDir+"/genesis.json") + state = state_.GenesisStateFromFile(stateDB, config.GenesisFile()) state.Save() } - // Get PrivAccount + // Get PrivValidator var privValidator *consensus.PrivValidator - if _, err := os.Stat(config.RootDir + "/private.json"); err == nil { - privAccount := state_.PrivAccountFromFile(config.RootDir + "/private.json") - privValidatorDB := db_.NewMemDB() // TODO configurable db. - privValidator = consensus.NewPrivValidator(privValidatorDB, privAccount) + if _, err := os.Stat(config.PrivValidatorFile()); err == nil { + privValidator = consensus.LoadPrivValidator() } // Get PEXReactor - book := p2p.NewAddrBook(config.RootDir + "/addrbook.json") + book := p2p.NewAddrBook(config.AddrBookFile()) pexReactor := p2p.NewPEXReactor(book) // Get MempoolReactor diff --git a/cmd/gen_account.go b/cmd/gen_account.go index e2355dbb..371e7cf5 100644 --- a/cmd/gen_account.go +++ b/cmd/gen_account.go @@ -4,13 +4,15 @@ import ( "encoding/base64" "fmt" - "github.com/tendermint/tendermint/state" + . "github.com/tendermint/tendermint/binary" + "github.com/tendermint/tendermint/wallet" ) func gen_account() { + // TODO: uh, write better logic. // Generate private account - privAccount := state.GenPrivAccount() + privAccount := wallet.GenPrivAccount() fmt.Printf(`Generated account: Account Public Key: %X @@ -19,7 +21,7 @@ Account Private Key: %X (base64) %v `, privAccount.PubKey, - base64.StdEncoding.EncodeToString(privAccount.PubKey), + base64.StdEncoding.EncodeToString(BinaryBytes(privAccount.PubKey)), privAccount.PrivKey, - base64.StdEncoding.EncodeToString(privAccount.PrivKey)) + base64.StdEncoding.EncodeToString(BinaryBytes(privAccount.PrivKey))) } diff --git a/cmd/gen_validator.go b/cmd/gen_validator.go new file mode 100644 index 00000000..14d822fd --- /dev/null +++ b/cmd/gen_validator.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "os" + + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/consensus" +) + +func gen_validator() { + + // If already exists, bail out. + filename := config.PrivValidatorFile() + if _, err := os.Stat(filename); !os.IsNotExist(err) { + fmt.Printf("Cannot generate new validator, file already exists at %v\n", filename) + } + + // Generate private validator + privValidator := consensus.GenPrivValidator() + privValidator.Save() + fmt.Printf("Generated a new validator at %v\n", filename) +} diff --git a/cmd/main.go b/cmd/main.go index b804c0c3..161ab4c7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -19,6 +19,7 @@ func main() { Commands: daemon Run the tendermint node daemon gen_account Generate new account keypair + gen_validator Generate new validator keypair tendermint --help for command options`) return @@ -29,5 +30,7 @@ tendermint --help for command options`) daemon() case "gen_account": gen_account() + case "gen_validator": + gen_validator() } } diff --git a/common/bit_array.go b/common/bit_array.go index 3d46184f..86ef2e4d 100644 --- a/common/bit_array.go +++ b/common/bit_array.go @@ -2,11 +2,8 @@ package common import ( "fmt" - "io" "math/rand" "strings" - - . "github.com/tendermint/tendermint/binary" ) // Not goroutine safe @@ -19,47 +16,6 @@ func NewBitArray(bits uint) BitArray { return BitArray{bits, make([]uint64, (bits+63)/64)} } -func ReadBitArray(r io.Reader, n *int64, err *error) BitArray { - bits := ReadUVarInt(r, n, err) - if bits == 0 { - return BitArray{} - } - elemsWritten := ReadUVarInt(r, n, err) - if *err != nil { - return BitArray{} - } - bA := NewBitArray(bits) - for i := uint(0); i < elemsWritten; i++ { - bA.elems[i] = ReadUInt64(r, n, err) - if *err != nil { - return BitArray{} - } - } - return bA -} - -func (bA BitArray) WriteTo(w io.Writer) (n int64, err error) { - WriteUVarInt(w, bA.bits, &n, &err) - if bA.bits == 0 { - return - } - // Count the last element > 0. - elemsToWrite := 0 - for i, elem := range bA.elems { - if elem > 0 { - elemsToWrite = i + 1 - } - } - WriteUVarInt(w, uint(elemsToWrite), &n, &err) - for i, elem := range bA.elems { - if i >= elemsToWrite { - break - } - WriteUInt64(w, elem, &n, &err) - } - return -} - func (bA BitArray) Size() uint { return bA.bits } diff --git a/config/config.go b/config/config.go index 0785237f..b8743e41 100644 --- a/config/config.go +++ b/config/config.go @@ -12,8 +12,22 @@ import ( "strings" ) -var RootDir string -var Config Config_ +var rootDir string + +func init() { + rootDir = os.Getenv("TMROOT") + if rootDir == "" { + rootDir = os.Getenv("HOME") + "/.tendermint" + } +} + +func ConfigFile() string { return rootDir + "/config.json" } +func GenesisFile() string { return rootDir + "/genesis.json" } +func AddrBookFile() string { return rootDir + "/addrbook.json" } +func PrivValidatorFile() string { return rootDir + "/priv_validator.json" } +func DataDir() string { return rootDir + "/data" } + +var Config ConfigType func setFlags(printHelp *bool) { flag.BoolVar(printHelp, "help", false, "Print this help message.") @@ -22,11 +36,7 @@ func setFlags(printHelp *bool) { } func ParseFlags() { - RootDir = os.Getenv("TMROOT") - if RootDir == "" { - RootDir = os.Getenv("HOME") + "/.tendermint" - } - configFile := RootDir + "/config.json" + configFile := ConfigFile() // try to read configuration. if missing, write default configBytes, err := ioutil.ReadFile(configFile) @@ -38,7 +48,7 @@ func ParseFlags() { } // try to parse configuration. on error, die - Config = Config_{} + Config = ConfigType{} err = json.Unmarshal(configBytes, &Config) if err != nil { log.Panicf("Invalid configuration file %s: %v", configFile, err) @@ -61,13 +71,13 @@ func ParseFlags() { //-----------------------------------------------------------------------------j // Default configuration -var defaultConfig = Config_{ +var defaultConfig = ConfigType{ Network: "tendermint_testnet0", LAddr: "0.0.0.0:0", SeedNode: "", Db: DbConfig{ Type: "level", - Dir: RootDir + "/data", + Dir: DataDir(), }, Alert: AlertConfig{}, SMTP: SMTPConfig{}, @@ -79,7 +89,7 @@ var defaultConfig = Config_{ //-----------------------------------------------------------------------------j // Configuration types -type Config_ struct { +type ConfigType struct { Network string LAddr string SeedNode string @@ -118,7 +128,7 @@ type RPCConfig struct { //-----------------------------------------------------------------------------j -func (cfg *Config_) validate() error { +func (cfg *ConfigType) validate() error { if cfg.Network == "" { cfg.Network = defaultConfig.Network } @@ -134,7 +144,7 @@ func (cfg *Config_) validate() error { return nil } -func (cfg *Config_) bytes() []byte { +func (cfg *ConfigType) bytes() []byte { configBytes, err := json.MarshalIndent(cfg, "", "\t") if err != nil { panic(err) @@ -142,7 +152,7 @@ func (cfg *Config_) bytes() []byte { return configBytes } -func (cfg *Config_) write(configFile string) { +func (cfg *ConfigType) write(configFile string) { if strings.Index(configFile, "/") != -1 { err := os.MkdirAll(filepath.Dir(configFile), 0700) if err != nil { diff --git a/consensus/pol.go b/consensus/pol.go index 174cc22b..96e2d010 100644 --- a/consensus/pol.go +++ b/consensus/pol.go @@ -2,111 +2,81 @@ package consensus import ( "fmt" - "io" - . "github.com/tendermint/tendermint/binary" + . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/state" ) +// Each signature of a POL (proof-of-lock, see whitepaper) is +// either a prevote or a commit. +// Commits require an additional round which is strictly less than +// the POL round. Prevote rounds are equal to the POL round. +type POLVoteSignature struct { + Round uint + Signature SignatureEd25519 +} + // Proof of lock. // +2/3 of validators' prevotes for a given blockhash (or nil) type POL struct { - Height uint32 - Round uint16 - BlockHash []byte // Could be nil, which makes this a proof of unlock. - BlockParts PartSetHeader // When BlockHash is nil, this is zero. - Votes []Signature // Vote signatures for height/round/hash - Commits []RoundSignature // Commit signatures for height/hash -} - -func ReadPOL(r io.Reader, n *int64, err *error) *POL { - return &POL{ - Height: ReadUInt32(r, n, err), - Round: ReadUInt16(r, n, err), - BlockHash: ReadByteSlice(r, n, err), - BlockParts: ReadPartSetHeader(r, n, err), - Votes: ReadSignatures(r, n, err), - Commits: ReadRoundSignatures(r, n, err), - } -} - -func (pol *POL) WriteTo(w io.Writer) (n int64, err error) { - WriteUInt32(w, pol.Height, &n, &err) - WriteUInt16(w, pol.Round, &n, &err) - WriteByteSlice(w, pol.BlockHash, &n, &err) - WriteBinary(w, pol.BlockParts, &n, &err) - WriteSignatures(w, pol.Votes, &n, &err) - WriteRoundSignatures(w, pol.Commits, &n, &err) - return + Height uint + Round uint + BlockHash []byte // Could be nil, which makes this a proof of unlock. + BlockParts PartSetHeader // When BlockHash is nil, this is zero. + Votes []POLVoteSignature // Prevote and commit signatures in ValidatorSet order. } // Returns whether +2/3 have voted/committed for BlockHash. -func (pol *POL) Verify(vset *state.ValidatorSet) error { +func (pol *POL) Verify(valSet *state.ValidatorSet) error { + + if uint(len(pol.Votes)) != valSet.Size() { + return Errorf("Invalid POL votes count: Expected %v, got %v", + valSet.Size(), len(pol.Votes)) + } talliedVotingPower := uint64(0) - voteDoc := BinaryBytes(&Vote{ + prevoteDoc := SignBytes(&Vote{ Height: pol.Height, Round: pol.Round, Type: VoteTypePrevote, BlockHash: pol.BlockHash, BlockParts: pol.BlockParts, }) - seenValidators := map[uint64]struct{}{} + seenValidators := map[string]struct{}{} - for _, sig := range pol.Votes { + for idx, sig := range pol.Votes { + voteDoc := prevoteDoc + _, val := valSet.GetByIndex(uint(idx)) + + // Commit signature? + if sig.Round < pol.Round { + voteDoc = SignBytes(&Vote{ + Height: pol.Height, Round: sig.Round, Type: VoteTypeCommit, + BlockHash: pol.BlockHash, + BlockParts: pol.BlockParts, + }) + } else if sig.Round > pol.Round { + return Errorf("Invalid commit round %v for POL %v", sig.Round, pol) + } // Validate - if _, seen := seenValidators[sig.SignerId]; seen { + if _, seen := seenValidators[string(val.Address)]; seen { return Errorf("Duplicate validator for vote %v for POL %v", sig, pol) } - _, val := vset.GetById(sig.SignerId) - if val == nil { - return Errorf("Invalid validator for vote %v for POL %v", sig, pol) - } - if !val.VerifyBytes(voteDoc, sig) { + if !val.PubKey.VerifyBytes(voteDoc, sig.Signature.Bytes) { return Errorf("Invalid signature for vote %v for POL %v", sig, pol) } // Tally - seenValidators[val.Id] = struct{}{} + seenValidators[string(val.Address)] = struct{}{} talliedVotingPower += val.VotingPower } - for _, rsig := range pol.Commits { - round := rsig.Round - sig := rsig.Signature - - // Validate - if _, seen := seenValidators[sig.SignerId]; seen { - return Errorf("Duplicate validator for commit %v for POL %v", sig, pol) - } - _, val := vset.GetById(sig.SignerId) - if val == nil { - return Errorf("Invalid validator for commit %v for POL %v", sig, pol) - } - if round >= pol.Round { - return Errorf("Invalid commit round %v for POL %v", round, pol) - } - - commitDoc := BinaryBytes(&Vote{ - Height: pol.Height, Round: round, Type: VoteTypeCommit, - BlockHash: pol.BlockHash, - BlockParts: pol.BlockParts, - }) - if !val.VerifyBytes(commitDoc, sig) { - return Errorf("Invalid signature for commit %v for POL %v", sig, pol) - } - - // Tally - seenValidators[val.Id] = struct{}{} - talliedVotingPower += val.VotingPower - } - - if talliedVotingPower > vset.TotalVotingPower()*2/3 { + if talliedVotingPower > valSet.TotalVotingPower()*2/3 { return nil } else { return Errorf("Invalid POL, insufficient voting power %v, needed %v", - talliedVotingPower, (vset.TotalVotingPower()*2/3 + 1)) + talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1)) } } diff --git a/consensus/priv_validator.go b/consensus/priv_validator.go index a7f19e9f..bf851516 100644 --- a/consensus/priv_validator.go +++ b/consensus/priv_validator.go @@ -1,30 +1,166 @@ package consensus +// TODO: This logic is crude. Should be more transactional. + import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + + . "github.com/tendermint/tendermint/account" + . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/blocks" - db_ "github.com/tendermint/tendermint/db" - "github.com/tendermint/tendermint/state" + . "github.com/tendermint/tendermint/common" + . "github.com/tendermint/tendermint/config" + + "github.com/tendermint/go-ed25519" ) -//----------------------------------------------------------------------------- +const ( + stepNone = 0 // Used to distinguish the initial state + stepPropose = 1 + stepPrevote = 2 + stepPrecommit = 3 + stepCommit = 4 +) -type PrivValidator struct { - db db_.DB - state.PrivAccount -} - -func NewPrivValidator(db db_.DB, priv *state.PrivAccount) *PrivValidator { - return &PrivValidator{db, *priv} -} - -// Double signing results in a panic. -func (pv *PrivValidator) Sign(o Signable) { - switch o.(type) { - case *Proposal: - //TODO: prevent double signing && test. - pv.PrivAccount.Sign(o.(*Proposal)) - case *Vote: - //TODO: prevent double signing && test. - pv.PrivAccount.Sign(o.(*Vote)) +func voteToStep(vote *Vote) uint8 { + switch vote.Type { + case VoteTypePrevote: + return stepPrevote + case VoteTypePrecommit: + return stepPrecommit + case VoteTypeCommit: + return stepCommit + default: + panic("Unknown vote type") + } +} + +type PrivValidator struct { + Address []byte + PubKey PubKeyEd25519 + PrivKey PrivKeyEd25519 + LastHeight uint + LastRound uint + LastStep uint8 +} + +// Generates a new validator with private key. +func GenPrivValidator() *PrivValidator { + privKeyBytes := CRandBytes(32) + pubKeyBytes := ed25519.MakePubKey(privKeyBytes) + pubKey := PubKeyEd25519{pubKeyBytes} + privKey := PrivKeyEd25519{pubKeyBytes, privKeyBytes} + return &PrivValidator{ + Address: pubKey.Address(), + PubKey: pubKey, + PrivKey: privKey, + LastHeight: 0, + LastRound: 0, + LastStep: stepNone, + } +} + +type PrivValidatorJSON struct { + Address string + PubKey string + PrivKey string + LastHeight uint + LastRound uint + LastStep uint8 +} + +func LoadPrivValidator() *PrivValidator { + privValJSONBytes, err := ioutil.ReadFile(PrivValidatorFile()) + if err != nil { + panic(err) + } + privValJSON := PrivValidatorJSON{} + err = json.Unmarshal(privValJSONBytes, &privValJSON) + if err != nil { + panic(err) + } + address, err := base64.StdEncoding.DecodeString(privValJSON.Address) + if err != nil { + panic(err) + } + pubKeyBytes, err := base64.StdEncoding.DecodeString(privValJSON.PubKey) + if err != nil { + panic(err) + } + privKeyBytes, err := base64.StdEncoding.DecodeString(privValJSON.PrivKey) + if err != nil { + panic(err) + } + n := new(int64) + privVal := &PrivValidator{ + Address: address, + PubKey: ReadBinary(PubKeyEd25519{}, bytes.NewReader(pubKeyBytes), n, &err).(PubKeyEd25519), + PrivKey: ReadBinary(PrivKeyEd25519{}, bytes.NewReader(privKeyBytes), n, &err).(PrivKeyEd25519), + LastHeight: privValJSON.LastHeight, + LastRound: privValJSON.LastRound, + LastStep: privValJSON.LastStep, + } + if err != nil { + panic(err) + } + return privVal +} + +func (privVal *PrivValidator) Save() { + privValJSON := PrivValidatorJSON{ + Address: base64.StdEncoding.EncodeToString(privVal.Address), + PubKey: base64.StdEncoding.EncodeToString(BinaryBytes(privVal.PubKey)), + PrivKey: base64.StdEncoding.EncodeToString(BinaryBytes(privVal.PrivKey)), + LastHeight: privVal.LastHeight, + LastRound: privVal.LastRound, + LastStep: privVal.LastStep, + } + privValJSONBytes, err := json.Marshal(privValJSON) + if err != nil { + panic(err) + } + err = ioutil.WriteFile(PrivValidatorFile(), privValJSONBytes, 0700) + if err != nil { + panic(err) + } +} + +func (privVal *PrivValidator) SignVote(vote *Vote) SignatureEd25519 { + if privVal.LastHeight < vote.Height || + privVal.LastHeight == vote.Height && privVal.LastRound < vote.Round || + privVal.LastHeight == vote.Height && privVal.LastRound == vote.Round && privVal.LastStep < voteToStep(vote) { + + // Persist height/round/step + privVal.LastHeight = vote.Height + privVal.LastRound = vote.Round + privVal.LastStep = voteToStep(vote) + privVal.Save() + + // Sign + return privVal.PrivKey.Sign(SignBytes(vote)).(SignatureEd25519) + } else { + panic(fmt.Sprintf("Attempt of duplicate signing of vote: Height %v, Round %v, Type %v", vote.Height, vote.Round, vote.Type)) + } +} + +func (privVal *PrivValidator) SignProposal(proposal *Proposal) SignatureEd25519 { + if privVal.LastHeight < proposal.Height || + privVal.LastHeight == proposal.Height && privVal.LastRound < proposal.Round || + privVal.LastHeight == 0 && privVal.LastRound == 0 && privVal.LastStep == stepNone { + + // Persist height/round/step + privVal.LastHeight = proposal.Height + privVal.LastRound = proposal.Round + privVal.LastStep = stepPropose + privVal.Save() + + // Sign + return privVal.PrivKey.Sign(SignBytes(proposal)).(SignatureEd25519) + } else { + panic(fmt.Sprintf("Attempt of duplicate signing of proposal: Height %v, Round %v, Type %v", proposal.Height, proposal.Round)) } } diff --git a/consensus/proposal.go b/consensus/proposal.go index 0c52af60..cc64c310 100644 --- a/consensus/proposal.go +++ b/consensus/proposal.go @@ -5,6 +5,7 @@ import ( "fmt" "io" + . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/blocks" ) @@ -15,15 +16,14 @@ var ( ) type Proposal struct { - Height uint32 - Round uint16 + Height uint + Round uint BlockParts PartSetHeader POLParts PartSetHeader - Signature Signature + Signature SignatureEd25519 } -func NewProposal(height uint32, round uint16, blockParts, polParts PartSetHeader) *Proposal { - +func NewProposal(height uint, round uint, blockParts, polParts PartSetHeader) *Proposal { return &Proposal{ Height: height, Round: round, @@ -32,34 +32,14 @@ func NewProposal(height uint32, round uint16, blockParts, polParts PartSetHeader } } -func ReadProposal(r io.Reader, n *int64, err *error) *Proposal { - return &Proposal{ - Height: ReadUInt32(r, n, err), - Round: ReadUInt16(r, n, err), - BlockParts: ReadPartSetHeader(r, n, err), - POLParts: ReadPartSetHeader(r, n, err), - Signature: ReadSignature(r, n, err), - } -} - -func (p *Proposal) WriteTo(w io.Writer) (n int64, err error) { - WriteUInt32(w, p.Height, &n, &err) - WriteUInt16(w, p.Round, &n, &err) - WriteBinary(w, p.BlockParts, &n, &err) - WriteBinary(w, p.POLParts, &n, &err) - WriteBinary(w, p.Signature, &n, &err) - return -} - -func (p *Proposal) GetSignature() Signature { - return p.Signature -} - -func (p *Proposal) SetSignature(sig Signature) { - p.Signature = sig -} - func (p *Proposal) String() string { return fmt.Sprintf("Proposal{%v/%v %v %v %v}", p.Height, p.Round, p.BlockParts, p.POLParts, p.Signature) } + +func (p *Proposal) WriteSignBytes(w io.Writer, n *int64, err *error) { + WriteUVarInt(p.Height, w, n, err) + WriteUVarInt(p.Round, w, n, err) + WriteBinary(p.BlockParts, w, n, err) + WriteBinary(p.POLParts, w, n, err) +} diff --git a/consensus/reactor.go b/consensus/reactor.go index e009036e..fe0f2e56 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "fmt" - "io" "sync" "sync/atomic" "time" @@ -123,9 +122,9 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte msg := msg_.(*NewRoundStepMessage) ps.ApplyNewRoundStepMessage(msg, rs) - case *CommitMessage: - msg := msg_.(*CommitMessage) - ps.ApplyCommitMessage(msg) + case *CommitStepMessage: + msg := msg_.(*CommitStepMessage) + ps.ApplyCommitStepMessage(msg) case *HasVoteMessage: msg := msg_.(*HasVoteMessage) @@ -160,10 +159,17 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte case VoteCh: switch msg_.(type) { - case *Vote: - vote := msg_.(*Vote) - added, index, err := conR.conS.AddVote(vote) + case *VoteMessage: + voteMessage := msg_.(*VoteMessage) + vote := voteMessage.Vote + if rs.Height != vote.Height { + return // Wrong height. Not necessarily a bad peer. + } + validatorIndex := voteMessage.ValidatorIndex + address, _ := rs.Validators.GetByIndex(validatorIndex) + added, index, err := conR.conS.AddVote(address, vote) if err != nil { + // Probably an invalid signature. Bad peer. log.Warning("Error attempting to add vote: %v", err) } // Initialize Prevotes/Precommits/Commits if needed @@ -220,14 +226,14 @@ func (conR *ConsensusReactor) broadcastNewRoundStepRoutine() { Height: rs.Height, Round: rs.Round, Step: rs.Step, - SecondsSinceStartTime: uint32(timeElapsed.Seconds()), + SecondsSinceStartTime: uint(timeElapsed.Seconds()), } conR.sw.Broadcast(StateCh, msg) } - // If the step is commit, then also broadcast a CommitMessage. + // If the step is commit, then also broadcast a CommitStepMessage. if rs.Step == RoundStepCommit { - msg := &CommitMessage{ + msg := &CommitStepMessage{ Height: rs.Height, BlockParts: rs.ProposalBlockParts.Header(), BlockBitArray: rs.ProposalBlockParts.BitArray(), @@ -259,10 +265,10 @@ OUTER_LOOP: Height: rs.Height, Round: rs.Round, Type: partTypeProposalBlock, - Part: rs.ProposalBlockParts.GetPart(uint16(index)), + Part: rs.ProposalBlockParts.GetPart(index), } peer.Send(DataCh, msg) - ps.SetHasProposalBlockPart(rs.Height, rs.Round, uint16(index)) + ps.SetHasProposalBlockPart(rs.Height, rs.Round, index) continue OUTER_LOOP } } @@ -289,10 +295,10 @@ OUTER_LOOP: Height: rs.Height, Round: rs.Round, Type: partTypeProposalPOL, - Part: rs.ProposalPOLParts.GetPart(uint16(index)), + Part: rs.ProposalPOLParts.GetPart(index), } peer.Send(DataCh, msg) - ps.SetHasProposalPOLPart(rs.Height, rs.Round, uint16(index)) + ps.SetHasProposalPOLPart(rs.Height, rs.Round, index) continue OUTER_LOOP } } @@ -320,7 +326,7 @@ OUTER_LOOP: if ok { vote := voteSet.GetByIndex(index) // NOTE: vote may be a commit. - msg := p2p.TypedMessage{msgTypeVote, vote} + msg := &VoteMessage{index, vote} peer.Send(VoteCh, msg) ps.SetHasVote(vote, index) return true @@ -399,7 +405,7 @@ OUTER_LOOP: BlockParts: header.LastBlockParts, Signature: rsig.Signature, } - msg := p2p.TypedMessage{msgTypeVote, vote} + msg := &VoteMessage{index, vote} peer.Send(VoteCh, msg) ps.SetHasVote(vote, index) continue OUTER_LOOP @@ -416,8 +422,8 @@ OUTER_LOOP: // Read only when returned by PeerState.GetRoundState(). type PeerRoundState struct { - Height uint32 // Height peer is at - Round uint16 // Round peer is at + Height uint // Height peer is at + Round uint // Round peer is at Step RoundStep // Step peer is at StartTime time.Time // Estimated start of round 0 at this height Proposal bool // True if peer has proposal for this round @@ -474,7 +480,7 @@ func (ps *PeerState) SetHasProposal(proposal *Proposal) { ps.ProposalPOLBitArray = NewBitArray(uint(proposal.POLParts.Total)) } -func (ps *PeerState) SetHasProposalBlockPart(height uint32, round uint16, index uint16) { +func (ps *PeerState) SetHasProposalBlockPart(height uint, round uint, index uint) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -485,7 +491,7 @@ func (ps *PeerState) SetHasProposalBlockPart(height uint32, round uint16, index ps.ProposalBlockBitArray.SetIndex(uint(index), true) } -func (ps *PeerState) SetHasProposalPOLPart(height uint32, round uint16, index uint16) { +func (ps *PeerState) SetHasProposalPOLPart(height uint, round uint, index uint) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -496,7 +502,7 @@ func (ps *PeerState) SetHasProposalPOLPart(height uint32, round uint16, index ui ps.ProposalPOLBitArray.SetIndex(uint(index), true) } -func (ps *PeerState) EnsureVoteBitArrays(height uint32, numValidators uint) { +func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -521,7 +527,7 @@ func (ps *PeerState) SetHasVote(vote *Vote, index uint) { ps.setHasVote(vote.Height, vote.Round, vote.Type, index) } -func (ps *PeerState) setHasVote(height uint32, round uint16, type_ byte, index uint) { +func (ps *PeerState) setHasVote(height uint, round uint, type_ byte, index uint) { if ps.Height == height+1 && type_ == VoteTypeCommit { // Special case for LastCommits. ps.LastCommits.SetIndex(index, true) @@ -583,7 +589,7 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage, rs *Roun } } -func (ps *PeerState) ApplyCommitMessage(msg *CommitMessage) { +func (ps *PeerState) ApplyCommitStepMessage(msg *CommitStepMessage) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -614,15 +620,13 @@ func (ps *PeerState) ApplyHasVoteMessage(msg *HasVoteMessage) { // Messages const ( - msgTypeUnknown = byte(0x00) - // Messages for communicating state changes + msgTypeUnknown = byte(0x00) msgTypeNewRoundStep = byte(0x01) - msgTypeCommit = byte(0x02) - // Messages of data - msgTypeProposal = byte(0x11) - msgTypePart = byte(0x12) // both block & POL - msgTypeVote = byte(0x13) - msgTypeHasVote = byte(0x14) + msgTypeCommitStep = byte(0x02) + msgTypeProposal = byte(0x11) + msgTypePart = byte(0x12) // both block & POL + msgTypeVote = byte(0x13) + msgTypeHasVote = byte(0x14) ) // TODO: check for unnecessary extra bytes at the end. @@ -634,18 +638,18 @@ func decodeMessage(bz []byte) (msgType byte, msg interface{}) { switch msgType { // Messages for communicating state changes case msgTypeNewRoundStep: - msg = readNewRoundStepMessage(r, n, err) - case msgTypeCommit: - msg = readCommitMessage(r, n, err) + msg = ReadBinary(&NewRoundStepMessage{}, r, n, err) + case msgTypeCommitStep: + msg = ReadBinary(&CommitStepMessage{}, r, n, err) // Messages of data case msgTypeProposal: - msg = ReadProposal(r, n, err) + msg = ReadBinary(&Proposal{}, r, n, err) case msgTypePart: - msg = readPartMessage(r, n, err) + msg = ReadBinary(&PartMessage{}, r, n, err) case msgTypeVote: - msg = ReadVote(r, n, err) + msg = ReadBinary(&VoteMessage{}, r, n, err) case msgTypeHasVote: - msg = readHasVoteMessage(r, n, err) + msg = ReadBinary(&HasVoteMessage{}, r, n, err) default: msg = nil } @@ -655,29 +659,13 @@ func decodeMessage(bz []byte) (msgType byte, msg interface{}) { //------------------------------------- type NewRoundStepMessage struct { - Height uint32 - Round uint16 + Height uint + Round uint Step RoundStep - SecondsSinceStartTime uint32 + SecondsSinceStartTime uint } -func readNewRoundStepMessage(r io.Reader, n *int64, err *error) *NewRoundStepMessage { - return &NewRoundStepMessage{ - Height: ReadUInt32(r, n, err), - Round: ReadUInt16(r, n, err), - Step: RoundStep(ReadUInt8(r, n, err)), - SecondsSinceStartTime: ReadUInt32(r, n, err), - } -} - -func (m *NewRoundStepMessage) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, msgTypeNewRoundStep, &n, &err) - WriteUInt32(w, m.Height, &n, &err) - WriteUInt16(w, m.Round, &n, &err) - WriteUInt8(w, uint8(m.Step), &n, &err) - WriteUInt32(w, m.SecondsSinceStartTime, &n, &err) - return -} +func (m *NewRoundStepMessage) TypeByte() byte { return msgTypeNewRoundStep } func (m *NewRoundStepMessage) String() string { return fmt.Sprintf("[NewRoundStep %v/%v/%X]", m.Height, m.Round, m.Step) @@ -685,30 +673,16 @@ func (m *NewRoundStepMessage) String() string { //------------------------------------- -type CommitMessage struct { - Height uint32 +type CommitStepMessage struct { + Height uint BlockParts PartSetHeader BlockBitArray BitArray } -func readCommitMessage(r io.Reader, n *int64, err *error) *CommitMessage { - return &CommitMessage{ - Height: ReadUInt32(r, n, err), - BlockParts: ReadPartSetHeader(r, n, err), - BlockBitArray: ReadBitArray(r, n, err), - } -} +func (m *CommitStepMessage) TypeByte() byte { return msgTypeCommitStep } -func (m *CommitMessage) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, msgTypeCommit, &n, &err) - WriteUInt32(w, m.Height, &n, &err) - WriteBinary(w, m.BlockParts, &n, &err) - WriteBinary(w, m.BlockBitArray, &n, &err) - return -} - -func (m *CommitMessage) String() string { - return fmt.Sprintf("[Commit %v %v %v]", m.Height, m.BlockParts, m.BlockBitArray) +func (m *CommitStepMessage) String() string { + return fmt.Sprintf("[CommitStep %v %v %v]", m.Height, m.BlockParts, m.BlockBitArray) } //------------------------------------- @@ -719,29 +693,13 @@ const ( ) type PartMessage struct { - Height uint32 - Round uint16 + Height uint + Round uint Type byte Part *Part } -func readPartMessage(r io.Reader, n *int64, err *error) *PartMessage { - return &PartMessage{ - Height: ReadUInt32(r, n, err), - Round: ReadUInt16(r, n, err), - Type: ReadByte(r, n, err), - Part: ReadPart(r, n, err), - } -} - -func (m *PartMessage) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, msgTypePart, &n, &err) - WriteUInt32(w, m.Height, &n, &err) - WriteUInt16(w, m.Round, &n, &err) - WriteByte(w, m.Type, &n, &err) - WriteBinary(w, m.Part, &n, &err) - return -} +func (m *PartMessage) TypeByte() byte { return msgTypePart } func (m *PartMessage) String() string { return fmt.Sprintf("[Part %v/%v T:%X %v]", m.Height, m.Round, m.Type, m.Part) @@ -749,30 +707,27 @@ func (m *PartMessage) String() string { //------------------------------------- +type VoteMessage struct { + ValidatorIndex uint + Vote *Vote +} + +func (m *VoteMessage) TypeByte() byte { return msgTypeVote } + +func (m *VoteMessage) String() string { + return fmt.Sprintf("[Vote ValidatorIndex:%v Vote:%v]", m.ValidatorIndex, m.Vote) +} + +//------------------------------------- + type HasVoteMessage struct { - Height uint32 - Round uint16 + Height uint + Round uint Type byte Index uint } -func readHasVoteMessage(r io.Reader, n *int64, err *error) *HasVoteMessage { - return &HasVoteMessage{ - Height: ReadUInt32(r, n, err), - Round: ReadUInt16(r, n, err), - Type: ReadByte(r, n, err), - Index: ReadUVarInt(r, n, err), - } -} - -func (m *HasVoteMessage) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, msgTypeHasVote, &n, &err) - WriteUInt32(w, m.Height, &n, &err) - WriteUInt16(w, m.Round, &n, &err) - WriteByte(w, m.Type, &n, &err) - WriteUVarInt(w, m.Index, &n, &err) - return -} +func (m *HasVoteMessage) TypeByte() byte { return msgTypeHasVote } func (m *HasVoteMessage) String() string { return fmt.Sprintf("[HasVote %v/%v T:%X]", m.Height, m.Round, m.Type) diff --git a/consensus/state.go b/consensus/state.go index 1c2ee777..7dee95ed 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -52,6 +52,7 @@ Consensus State Machine Overview: package consensus import ( + "bytes" "errors" "fmt" "math" @@ -59,6 +60,7 @@ import ( "sync/atomic" "time" + . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/common" @@ -82,7 +84,7 @@ const ( RoundActionPrevote = RoundActionType(0xA1) // Prevote and goto RoundStepPrevote RoundActionPrecommit = RoundActionType(0xA2) // Precommit and goto RoundStepPrecommit RoundActionTryCommit = RoundActionType(0xC0) // Goto RoundStepCommit, or RoundStepPropose for next round. - RoundActionCommit = RoundActionType(0xC1) // Goto RoundStepCommit + RoundActionCommit = RoundActionType(0xC1) // Goto RoundStepCommit upon +2/3 commits RoundActionTryFinalize = RoundActionType(0xC2) // Maybe goto RoundStepPropose for next round. roundDuration0 = 60 * time.Second // The first round is 60 seconds long. @@ -97,8 +99,8 @@ var ( ) type RoundAction struct { - Height uint32 // The block height for which consensus is reaching for. - Round uint16 // The round number at given height. + Height uint // The block height for which consensus is reaching for. + Round uint // The round number at given height. Action RoundActionType // Action to perform. } @@ -106,8 +108,8 @@ type RoundAction struct { // Immutable when returned from ConsensusState.GetRoundState() type RoundState struct { - Height uint32 // Height we are working on - Round uint16 + Height uint // Height we are working on + Round uint Step RoundStep StartTime time.Time CommitTime time.Time // Time when +2/3 commits were found @@ -347,7 +349,6 @@ ACTION_LOOP: if rs.Precommits.HasTwoThirdsMajority() { // Enter RoundStepCommit and commit. cs.RunActionCommit(rs.Height) - cs.queueAction(RoundAction{rs.Height, rs.Round, RoundActionTryFinalize}) continue ACTION_LOOP } else { // Could not commit, move onto next round. @@ -363,7 +364,6 @@ ACTION_LOOP: } // Enter RoundStepCommit and commit. cs.RunActionCommit(rs.Height) - cs.queueAction(RoundAction{rs.Height, rs.Round, RoundActionTryFinalize}) continue ACTION_LOOP case RoundActionTryFinalize: @@ -435,7 +435,7 @@ func (cs *ConsensusState) updateToState(state *state.State) { } // After the call cs.Step becomes RoundStepNewRound. -func (cs *ConsensusState) setupNewRound(round uint16) { +func (cs *ConsensusState) setupNewRound(round uint) { // Sanity check if round == 0 { panic("setupNewRound() should never be called for round 0") @@ -470,7 +470,7 @@ func (cs *ConsensusState) SetPrivValidator(priv *PrivValidator) { //----------------------------------------------------------------------------- // Set up the round to desired round and set step to RoundStepNewRound -func (cs *ConsensusState) SetupNewRound(height uint32, desiredRound uint16) bool { +func (cs *ConsensusState) SetupNewRound(height uint, desiredRound uint) bool { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height { @@ -485,7 +485,7 @@ func (cs *ConsensusState) SetupNewRound(height uint32, desiredRound uint16) bool return true } -func (cs *ConsensusState) RunActionPropose(height uint32, round uint16) { +func (cs *ConsensusState) RunActionPropose(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || cs.Round != round { @@ -497,7 +497,7 @@ func (cs *ConsensusState) RunActionPropose(height uint32, round uint16) { }() // Nothing to do if it's not our turn. - if cs.PrivValidator == nil || cs.Validators.Proposer().Id != cs.PrivValidator.Id { + if cs.PrivValidator == nil || !bytes.Equal(cs.Validators.Proposer().Address, cs.PrivValidator.Address) { return } @@ -560,7 +560,7 @@ func (cs *ConsensusState) RunActionPropose(height uint32, round uint16) { // Make proposal proposal := NewProposal(cs.Height, cs.Round, blockParts.Header(), polParts.Header()) - cs.PrivValidator.Sign(proposal) + proposal.Signature = cs.PrivValidator.SignProposal(proposal) // Set fields cs.Proposal = proposal @@ -572,7 +572,7 @@ func (cs *ConsensusState) RunActionPropose(height uint32, round uint16) { // Prevote for LockedBlock if we're locked, or ProposealBlock if valid. // Otherwise vote nil. -func (cs *ConsensusState) RunActionPrevote(height uint32, round uint16) { +func (cs *ConsensusState) RunActionPrevote(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || cs.Round != round { @@ -612,7 +612,7 @@ func (cs *ConsensusState) RunActionPrevote(height uint32, round uint16) { // Lock & Precommit the ProposalBlock if we have enough prevotes for it, // or unlock an existing lock if +2/3 of prevotes were nil. -func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) { +func (cs *ConsensusState) RunActionPrecommit(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || cs.Round != round { @@ -668,7 +668,11 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) { } // Enter commit step. See the diagram for details. -func (cs *ConsensusState) RunActionCommit(height uint32) { +// There are two ways to enter this step: +// * After the Precommit step with +2/3 precommits, or, +// * Upon +2/3 commits regardless of current step +// Either way this action is run at most once per round. +func (cs *ConsensusState) RunActionCommit(height uint) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height { @@ -679,6 +683,7 @@ func (cs *ConsensusState) RunActionCommit(height uint32) { cs.newStepCh <- cs.getRoundState() }() + // Sanity check. // There are two ways to enter: // 1. +2/3 precommits at the end of RoundStepPrecommit // 2. +2/3 commits at any time @@ -712,15 +717,20 @@ func (cs *ConsensusState) RunActionCommit(height uint32) { } } else { // We have the block, so save/stage/sign-commit-vote. + cs.saveCommitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts) + } - cs.processBlockForCommit(cs.ProposalBlock, cs.ProposalBlockParts) + // If we have the block AND +2/3 commits, queue RoundActionTryFinalize. + // Round will immediately become finalized. + if cs.ProposalBlock.HashesTo(hash) && cs.Commits.HasTwoThirdsMajority() { + cs.queueAction(RoundAction{cs.Height, cs.Round, RoundActionTryFinalize}) } } // Returns true if Finalize happened, which increments height && sets // the step to RoundStepNewHeight (or RoundStepNewRound, but probably not). -func (cs *ConsensusState) TryFinalizeCommit(height uint32) bool { +func (cs *ConsensusState) TryFinalizeCommit(height uint) bool { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -754,6 +764,7 @@ func (cs *ConsensusState) TryFinalizeCommit(height uint32) bool { return true } else { // Prevent zombies. + // TODO: Does this ever happen? Panicf("+2/3 committed an invalid block: %v", err) } } @@ -782,7 +793,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { } // Verify signature - if !cs.Validators.Proposer().Verify(proposal) { + if !cs.Validators.Proposer().PubKey.VerifyBytes(SignBytes(proposal), proposal.Signature) { return ErrInvalidProposalSignature } @@ -794,7 +805,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { // NOTE: block is not necessarily valid. // NOTE: This function may increment the height. -func (cs *ConsensusState) AddProposalBlockPart(height uint32, round uint16, part *Part) (added bool, err error) { +func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *Part) (added bool, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -815,8 +826,11 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint32, round uint16, part if added && cs.ProposalBlockParts.IsComplete() { var n int64 var err error - cs.ProposalBlock = ReadBlock(cs.ProposalBlockParts.GetReader(), &n, &err) - cs.queueAction(RoundAction{cs.Height, cs.Round, RoundActionTryFinalize}) + cs.ProposalBlock = ReadBinary(&Block{}, cs.ProposalBlockParts.GetReader(), &n, &err).(*Block) + // If we're already in the commit step, try to finalize round. + if cs.Step == RoundStepCommit { + cs.queueAction(RoundAction{cs.Height, cs.Round, RoundActionTryFinalize}) + } // XXX If POL is valid, consider unlocking. return true, err } @@ -824,7 +838,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint32, round uint16, part } // NOTE: POL is not necessarily valid. -func (cs *ConsensusState) AddProposalPOLPart(height uint32, round uint16, part *Part) (added bool, err error) { +func (cs *ConsensusState) AddProposalPOLPart(height uint, round uint, part *Part) (added bool, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -844,21 +858,21 @@ func (cs *ConsensusState) AddProposalPOLPart(height uint32, round uint16, part * if added && cs.ProposalPOLParts.IsComplete() { var n int64 var err error - cs.ProposalPOL = ReadPOL(cs.ProposalPOLParts.GetReader(), &n, &err) + cs.ProposalPOL = ReadBinary(&POL{}, cs.ProposalPOLParts.GetReader(), &n, &err).(*POL) return true, err } return true, nil } -func (cs *ConsensusState) AddVote(vote *Vote) (added bool, index uint, err error) { +func (cs *ConsensusState) AddVote(address []byte, vote *Vote) (added bool, index uint, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() - return cs.addVote(vote) + return cs.addVote(address, vote) } // TODO: Maybe move this out of here? -func (cs *ConsensusState) LoadHeaderValidation(height uint32) (*Header, *Validation) { +func (cs *ConsensusState) LoadHeaderValidation(height uint) (*Header, *Validation) { meta := cs.blockStore.LoadBlockMeta(height) if meta == nil { return nil, nil @@ -869,21 +883,21 @@ func (cs *ConsensusState) LoadHeaderValidation(height uint32) (*Header, *Validat //----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(vote *Vote) (added bool, index uint, err error) { +func (cs *ConsensusState) addVote(address []byte, vote *Vote) (added bool, index uint, err error) { switch vote.Type { case VoteTypePrevote: // Prevotes checks for height+round match. - return cs.Prevotes.Add(vote) + return cs.Prevotes.Add(address, vote) case VoteTypePrecommit: // Precommits checks for height+round match. - return cs.Precommits.Add(vote) + return cs.Precommits.Add(address, vote) case VoteTypeCommit: if vote.Height == cs.Height { // No need to check if vote.Round < cs.Round ... // Prevotes && Precommits already checks that. - cs.Prevotes.Add(vote) - cs.Precommits.Add(vote) - added, index, err = cs.Commits.Add(vote) + cs.Prevotes.Add(address, vote) + cs.Precommits.Add(address, vote) + added, index, err = cs.Commits.Add(address, vote) if added && cs.Commits.HasTwoThirdsMajority() && cs.CommitTime.IsZero() { cs.CommitTime = time.Now() log.Debug("Set CommitTime to %v", cs.CommitTime) @@ -896,7 +910,7 @@ func (cs *ConsensusState) addVote(vote *Vote) (added bool, index uint, err error return added, index, err } if vote.Height+1 == cs.Height { - return cs.LastCommits.Add(vote) + return cs.LastCommits.Add(address, vote) } return false, 0, nil default: @@ -930,7 +944,7 @@ func (cs *ConsensusState) stageBlock(block *Block, blockParts *PartSet) error { } func (cs *ConsensusState) signAddVote(type_ byte, hash []byte, header PartSetHeader) *Vote { - if cs.PrivValidator == nil || !cs.Validators.HasId(cs.PrivValidator.Id) { + if cs.PrivValidator == nil || !cs.Validators.HasAddress(cs.PrivValidator.Address) { return nil } vote := &Vote{ @@ -940,12 +954,12 @@ func (cs *ConsensusState) signAddVote(type_ byte, hash []byte, header PartSetHea BlockHash: hash, BlockParts: header, } - cs.PrivValidator.Sign(vote) - cs.addVote(vote) + vote.Signature = cs.PrivValidator.SignVote(vote) + cs.addVote(cs.PrivValidator.Address, vote) return vote } -func (cs *ConsensusState) processBlockForCommit(block *Block, blockParts *PartSet) { +func (cs *ConsensusState) saveCommitVoteBlock(block *Block, blockParts *PartSet) { // The proposal must be valid. if err := cs.stageBlock(block, blockParts); err != nil { @@ -969,19 +983,19 @@ func (cs *ConsensusState) processBlockForCommit(block *Block, blockParts *PartSe //----------------------------------------------------------------------------- // total duration of given round -func calcRoundDuration(round uint16) time.Duration { +func calcRoundDuration(round uint) time.Duration { return roundDuration0 + roundDurationDelta*time.Duration(round) } // startTime is when round zero started. -func calcRoundStartTime(round uint16, startTime time.Time) time.Time { +func calcRoundStartTime(round uint, startTime time.Time) time.Time { return startTime.Add(roundDuration0*time.Duration(round) + roundDurationDelta*(time.Duration((int64(round)*int64(round)-int64(round))/2))) } // calculates the current round given startTime of round zero. // NOTE: round is zero if startTime is in the future. -func calcRound(startTime time.Time) uint16 { +func calcRound(startTime time.Time) uint { now := time.Now() if now.Before(startTime) { return 0 @@ -997,18 +1011,18 @@ func calcRound(startTime time.Time) uint16 { if math.IsNaN(R) { panic("Could not calc round, should not happen") } - if R > math.MaxInt16 { + if R > math.MaxInt32 { Panicf("Could not calc round, round overflow: %v", R) } if R < 0 { return 0 } - return uint16(R) + return uint(R) } // convenience // NOTE: elapsedRatio can be negative if startTime is in the future. -func calcRoundInfo(startTime time.Time) (round uint16, roundStartTime time.Time, roundDuration time.Duration, +func calcRoundInfo(startTime time.Time) (round uint, roundStartTime time.Time, roundDuration time.Duration, roundElapsed time.Duration, elapsedRatio float64) { round = calcRound(startTime) roundStartTime = calcRoundStartTime(round, startTime) diff --git a/consensus/test.go b/consensus/test.go index 8112c802..56348ba8 100644 --- a/consensus/test.go +++ b/consensus/test.go @@ -6,23 +6,27 @@ import ( // Common test methods -func makeValidator(id uint64, votingPower uint64) (*state.Validator, *state.PrivAccount) { - privAccount := state.GenPrivAccount() - privAccount.Id = id +func makeValidator(votingPower uint64) (*state.Validator, *PrivValidator) { + privValidator := GenPrivValidator() return &state.Validator{ - Account: privAccount.Account, - VotingPower: votingPower, - }, privAccount + Address: privValidator.Address, + PubKey: privValidator.PubKey, + BondHeight: 0, + UnbondHeight: 0, + LastCommitHeight: 0, + VotingPower: votingPower, + Accum: 0, + }, privValidator } -func makeVoteSet(height uint32, round uint16, type_ byte, numValidators int, votingPower uint64) (*VoteSet, *state.ValidatorSet, []*state.PrivAccount) { +func makeVoteSet(height uint, round uint, type_ byte, numValidators int, votingPower uint64) (*VoteSet, *state.ValidatorSet, []*PrivValidator) { vals := make([]*state.Validator, numValidators) - privAccounts := make([]*state.PrivAccount, numValidators) + privValidators := make([]*PrivValidator, numValidators) for i := 0; i < numValidators; i++ { - val, privAccount := makeValidator(uint64(i), votingPower) + val, privValidator := makeValidator(votingPower) vals[i] = val - privAccounts[i] = privAccount + privValidators[i] = privValidator } valSet := state.NewValidatorSet(vals) - return NewVoteSet(height, round, type_, valSet), valSet, privAccounts + return NewVoteSet(height, round, type_, valSet), valSet, privValidators } diff --git a/consensus/vote_set.go b/consensus/vote_set.go index d6b94fdf..73bfc83c 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -6,6 +6,7 @@ import ( "strings" "sync" + . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/common" @@ -16,17 +17,16 @@ import ( // for a predefined vote type. // Note that there three kinds of votes: prevotes, precommits, and commits. // A commit of prior rounds can be added added in lieu of votes/precommits. -// TODO: test majority calculations etc. -// NOTE: assumes that the sum total of voting power does not exceed MaxUInt64. +// NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64. type VoteSet struct { - height uint32 - round uint16 + height uint + round uint type_ byte mtx sync.Mutex - vset *state.ValidatorSet - votes map[uint64]*Vote - votesBitArray BitArray + valSet *state.ValidatorSet + votes []*Vote // validator index -> vote + votesBitArray BitArray // validator index -> has vote? votesByBlock map[string]uint64 // string(blockHash)+string(blockParts) -> vote sum. totalVotes uint64 maj23Hash []byte @@ -35,7 +35,7 @@ type VoteSet struct { } // Constructs a new VoteSet struct used to accumulate votes for each round. -func NewVoteSet(height uint32, round uint16, type_ byte, vset *state.ValidatorSet) *VoteSet { +func NewVoteSet(height uint, round uint, type_ byte, valSet *state.ValidatorSet) *VoteSet { if height == 0 { panic("Cannot make VoteSet for height == 0, doesn't make sense.") } @@ -46,55 +46,55 @@ func NewVoteSet(height uint32, round uint16, type_ byte, vset *state.ValidatorSe height: height, round: round, type_: type_, - vset: vset, - votes: make(map[uint64]*Vote, vset.Size()), - votesBitArray: NewBitArray(vset.Size()), + valSet: valSet, + votes: make([]*Vote, valSet.Size()), + votesBitArray: NewBitArray(valSet.Size()), votesByBlock: make(map[string]uint64), totalVotes: 0, } } -func (vs *VoteSet) Size() uint { - if vs == nil { +func (voteSet *VoteSet) Size() uint { + if voteSet == nil { return 0 } else { - return vs.vset.Size() + return voteSet.valSet.Size() } } // True if added, false if not. // Returns ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] // NOTE: vote should not be mutated after adding. -func (vs *VoteSet) Add(vote *Vote) (bool, uint, error) { - vs.mtx.Lock() - defer vs.mtx.Unlock() +func (voteSet *VoteSet) Add(address []byte, vote *Vote) (bool, uint, error) { + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() - // Make sure the step matches. (or that vote is commit && round < vs.round) - if vote.Height != vs.height || - (vote.Type != VoteTypeCommit && vote.Round != vs.round) || - (vote.Type != VoteTypeCommit && vote.Type != vs.type_) || - (vote.Type == VoteTypeCommit && vs.type_ != VoteTypeCommit && vote.Round >= vs.round) { + // Make sure the step matches. (or that vote is commit && round < voteSet.round) + if vote.Height != voteSet.height || + (vote.Type != VoteTypeCommit && vote.Round != voteSet.round) || + (vote.Type != VoteTypeCommit && vote.Type != voteSet.type_) || + (vote.Type == VoteTypeCommit && voteSet.type_ != VoteTypeCommit && vote.Round >= voteSet.round) { return false, 0, ErrVoteUnexpectedStep } // Ensure that signer is a validator. - _, val := vs.vset.GetById(vote.SignerId) + valIndex, val := voteSet.valSet.GetByAddress(address) if val == nil { return false, 0, ErrVoteInvalidAccount } // Check signature. - if !val.Verify(vote) { + if !val.PubKey.VerifyBytes(SignBytes(vote), vote.Signature) { // Bad signature. return false, 0, ErrVoteInvalidSignature } - return vs.addVote(vote) + return voteSet.addVote(valIndex, vote) } -func (vs *VoteSet) addVote(vote *Vote) (bool, uint, error) { +func (voteSet *VoteSet) addVote(valIndex uint, vote *Vote) (bool, uint, error) { // If vote already exists, return false. - if existingVote, ok := vs.votes[vote.SignerId]; ok { + if existingVote := voteSet.votes[valIndex]; existingVote != nil { if bytes.Equal(existingVote.BlockHash, vote.BlockHash) { return false, 0, nil } else { @@ -103,170 +103,154 @@ func (vs *VoteSet) addVote(vote *Vote) (bool, uint, error) { } // Add vote. - vs.votes[vote.SignerId] = vote - voterIndex, val := vs.vset.GetById(vote.SignerId) + _, val := voteSet.valSet.GetByIndex(valIndex) if val == nil { - return false, 0, ErrVoteInvalidAccount + panic(fmt.Sprintf("Missing validator for index %v", valIndex)) } - vs.votesBitArray.SetIndex(uint(voterIndex), true) + voteSet.votes[valIndex] = vote + voteSet.votesBitArray.SetIndex(valIndex, true) blockKey := string(vote.BlockHash) + string(BinaryBytes(vote.BlockParts)) - totalBlockHashVotes := vs.votesByBlock[blockKey] + val.VotingPower - vs.votesByBlock[blockKey] = totalBlockHashVotes - vs.totalVotes += val.VotingPower + totalBlockHashVotes := voteSet.votesByBlock[blockKey] + val.VotingPower + voteSet.votesByBlock[blockKey] = totalBlockHashVotes + voteSet.totalVotes += val.VotingPower // If we just nudged it up to two thirds majority, add it. - if totalBlockHashVotes > vs.vset.TotalVotingPower()*2/3 && - (totalBlockHashVotes-val.VotingPower) <= vs.vset.TotalVotingPower()*2/3 { - vs.maj23Hash = vote.BlockHash - vs.maj23Parts = vote.BlockParts - vs.maj23Exists = true + if totalBlockHashVotes > voteSet.valSet.TotalVotingPower()*2/3 && + (totalBlockHashVotes-val.VotingPower) <= voteSet.valSet.TotalVotingPower()*2/3 { + voteSet.maj23Hash = vote.BlockHash + voteSet.maj23Parts = vote.BlockParts + voteSet.maj23Exists = true } - return true, voterIndex, nil + return true, valIndex, nil } // Assumes that commits VoteSet is valid. -func (vs *VoteSet) AddFromCommits(commits *VoteSet) { - commitVotes := commits.AllVotes() - for _, commit := range commitVotes { - if commit.Round < vs.round { - vs.addVote(commit) +func (voteSet *VoteSet) AddFromCommits(commits *VoteSet) { + for valIndex, commit := range commits.votes { + if commit.Round < voteSet.round { + voteSet.addVote(uint(valIndex), commit) } } } -func (vs *VoteSet) BitArray() BitArray { - if vs == nil { +func (voteSet *VoteSet) BitArray() BitArray { + if voteSet == nil { return BitArray{} } - vs.mtx.Lock() - defer vs.mtx.Unlock() - return vs.votesBitArray.Copy() + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + return voteSet.votesBitArray.Copy() } -func (vs *VoteSet) GetByIndex(index uint) *Vote { - vs.mtx.Lock() - defer vs.mtx.Unlock() +func (voteSet *VoteSet) GetByIndex(valIndex uint) *Vote { + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + return voteSet.votes[valIndex] +} - id, val := vs.vset.GetByIndex(index) +func (voteSet *VoteSet) GetByAddress(address []byte) *Vote { + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + valIndex, val := voteSet.valSet.GetByAddress(address) if val == nil { - panic("GetByIndex(index) returned nil") + panic("GetByAddress(address) returned nil") } - - return vs.votes[id] + return voteSet.votes[valIndex] } -func (vs *VoteSet) GetById(id uint64) *Vote { - vs.mtx.Lock() - defer vs.mtx.Unlock() - return vs.votes[id] -} - -func (vs *VoteSet) AllVotes() []*Vote { - vs.mtx.Lock() - defer vs.mtx.Unlock() - votes := []*Vote{} - for _, vote := range vs.votes { - votes = append(votes, vote) - } - return votes -} - -func (vs *VoteSet) HasTwoThirdsMajority() bool { - if vs == nil { +func (voteSet *VoteSet) HasTwoThirdsMajority() bool { + if voteSet == nil { return false } - vs.mtx.Lock() - defer vs.mtx.Unlock() - return vs.maj23Exists + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + return voteSet.maj23Exists } // Returns either a blockhash (or nil) that received +2/3 majority. // If there exists no such majority, returns (nil, false). -func (vs *VoteSet) TwoThirdsMajority() (hash []byte, parts PartSetHeader, ok bool) { - vs.mtx.Lock() - defer vs.mtx.Unlock() - if vs.maj23Exists { - return vs.maj23Hash, vs.maj23Parts, true +func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts PartSetHeader, ok bool) { + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + if voteSet.maj23Exists { + return voteSet.maj23Hash, voteSet.maj23Parts, true } else { return nil, PartSetHeader{}, false } } -func (vs *VoteSet) MakePOL() *POL { - if vs.type_ != VoteTypePrevote { +func (voteSet *VoteSet) MakePOL() *POL { + if voteSet.type_ != VoteTypePrevote { panic("Cannot MakePOL() unless VoteSet.Type is VoteTypePrevote") } - vs.mtx.Lock() - defer vs.mtx.Unlock() - if !vs.maj23Exists { + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + if !voteSet.maj23Exists { return nil } pol := &POL{ - Height: vs.height, - Round: vs.round, - BlockHash: vs.maj23Hash, - BlockParts: vs.maj23Parts, + Height: voteSet.height, + Round: voteSet.round, + BlockHash: voteSet.maj23Hash, + BlockParts: voteSet.maj23Parts, + Votes: make([]POLVoteSignature, voteSet.valSet.Size()), } - for _, vote := range vs.votes { - if !bytes.Equal(vote.BlockHash, vs.maj23Hash) { + for valIndex, vote := range voteSet.votes { + if !bytes.Equal(vote.BlockHash, voteSet.maj23Hash) { continue } - if !vote.BlockParts.Equals(vs.maj23Parts) { + if !vote.BlockParts.Equals(voteSet.maj23Parts) { continue } - if vote.Type == VoteTypePrevote { - pol.Votes = append(pol.Votes, vote.Signature) - } else if vote.Type == VoteTypeCommit { - pol.Commits = append(pol.Commits, RoundSignature{vote.Round, vote.Signature}) - } else { - Panicf("Unexpected vote type %X", vote.Type) + pol.Votes[valIndex] = POLVoteSignature{ + Round: vote.Round, + Signature: vote.Signature, } } return pol } -func (vs *VoteSet) MakeValidation() *Validation { - if vs.type_ != VoteTypeCommit { +func (voteSet *VoteSet) MakeValidation() *Validation { + if voteSet.type_ != VoteTypeCommit { panic("Cannot MakeValidation() unless VoteSet.Type is VoteTypeCommit") } - vs.mtx.Lock() - defer vs.mtx.Unlock() - if len(vs.maj23Hash) == 0 { + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + if len(voteSet.maj23Hash) == 0 { panic("Cannot MakeValidation() unless a blockhash has +2/3") } - rsigs := make([]RoundSignature, vs.vset.Size()) - vs.vset.Iterate(func(index uint, val *state.Validator) bool { - vote := vs.votes[val.Id] - + commits := make([]Commit, voteSet.valSet.Size()) + voteSet.valSet.Iterate(func(valIndex uint, val *state.Validator) bool { + vote := voteSet.votes[valIndex] if vote == nil { return false } - if !bytes.Equal(vote.BlockHash, vs.maj23Hash) { + if !bytes.Equal(vote.BlockHash, voteSet.maj23Hash) { return false } - if !vote.BlockParts.Equals(vs.maj23Parts) { + if !vote.BlockParts.Equals(voteSet.maj23Parts) { return false } - rsigs[index] = RoundSignature{vote.Round, vote.Signature} + commits[valIndex] = Commit{vote.Round, vote.Signature} return false }) return &Validation{ - Commits: rsigs, + Commits: commits, } } -func (vs *VoteSet) String() string { - return vs.StringWithIndent("") +func (voteSet *VoteSet) String() string { + return voteSet.StringWithIndent("") } -func (vs *VoteSet) StringWithIndent(indent string) string { - vs.mtx.Lock() - defer vs.mtx.Unlock() +func (voteSet *VoteSet) StringWithIndent(indent string) string { + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() - voteStrings := make([]string, len(vs.votes)) + voteStrings := make([]string, len(voteSet.votes)) counter := 0 - for _, vote := range vs.votes { + for _, vote := range voteSet.votes { voteStrings[counter] = vote.String() counter++ } @@ -275,18 +259,18 @@ func (vs *VoteSet) StringWithIndent(indent string) string { %s %v %s %v %s}`, - indent, vs.height, vs.round, vs.type_, + indent, voteSet.height, voteSet.round, voteSet.type_, indent, strings.Join(voteStrings, "\n"+indent+" "), - indent, vs.votesBitArray, + indent, voteSet.votesBitArray, indent) } -func (vs *VoteSet) Description() string { - if vs == nil { +func (voteSet *VoteSet) Description() string { + if voteSet == nil { return "nil-VoteSet" } - vs.mtx.Lock() - defer vs.mtx.Unlock() + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() return fmt.Sprintf(`VoteSet{H:%v R:%v T:%v %v}`, - vs.height, vs.round, vs.type_, vs.votesBitArray) + voteSet.height, voteSet.round, voteSet.type_, voteSet.votesBitArray) } diff --git a/db/db.go b/db/db.go index d44f4078..122f88a9 100644 --- a/db/db.go +++ b/db/db.go @@ -3,4 +3,7 @@ package db type DB interface { Get([]byte) []byte Set([]byte, []byte) + SetSync([]byte, []byte) + Delete([]byte) + DeleteSync([]byte) } diff --git a/db/level_db.go b/db/level_db.go index e6956404..aa9a222d 100644 --- a/db/level_db.go +++ b/db/level_db.go @@ -3,6 +3,7 @@ package db import ( "fmt" "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" "path" ) @@ -20,13 +21,6 @@ func NewLevelDB(name string) (*LevelDB, error) { return database, nil } -func (db *LevelDB) Set(key []byte, value []byte) { - err := db.db.Put(key, value, nil) - if err != nil { - panic(err) - } -} - func (db *LevelDB) Get(key []byte) []byte { res, err := db.db.Get(key, nil) if err != nil { @@ -35,6 +29,20 @@ func (db *LevelDB) Get(key []byte) []byte { return res } +func (db *LevelDB) Set(key []byte, value []byte) { + err := db.db.Put(key, value, nil) + if err != nil { + panic(err) + } +} + +func (db *LevelDB) SetSync(key []byte, value []byte) { + err := db.db.Put(key, value, &opt.WriteOptions{Sync: true}) + if err != nil { + panic(err) + } +} + func (db *LevelDB) Delete(key []byte) { err := db.db.Delete(key, nil) if err != nil { @@ -42,6 +50,13 @@ func (db *LevelDB) Delete(key []byte) { } } +func (db *LevelDB) DeleteSync(key []byte) { + err := db.db.Delete(key, &opt.WriteOptions{Sync: true}) + if err != nil { + panic(err) + } +} + func (db *LevelDB) Db() *leveldb.DB { return db.db } diff --git a/db/mem_db.go b/db/mem_db.go index 3824872e..9b9b0d20 100644 --- a/db/mem_db.go +++ b/db/mem_db.go @@ -13,18 +13,26 @@ func NewMemDB() *MemDB { return database } -func (db *MemDB) Set(key []byte, value []byte) { - db.db[string(key)] = value -} - func (db *MemDB) Get(key []byte) []byte { return db.db[string(key)] } +func (db *MemDB) Set(key []byte, value []byte) { + db.db[string(key)] = value +} + +func (db *MemDB) SetSync(key []byte, value []byte) { + db.db[string(key)] = value +} + func (db *MemDB) Delete(key []byte) { delete(db.db, string(key)) } +func (db *MemDB) DeleteSync(key []byte) { + delete(db.db, string(key)) +} + func (db *MemDB) Print() { for key, value := range db.db { fmt.Printf("[%X]:\t[%X]\n", []byte(key), value) diff --git a/mempool/mempool.go b/mempool/mempool.go index 284f0458..631890f6 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -59,14 +59,14 @@ func (mem *Mempool) ResetForBlockAndState(block *Block, state *state.State) { // First, create a lookup map of txns in new block. blockTxsMap := make(map[string]struct{}) for _, tx := range block.Data.Txs { - txHash := BinaryHash(tx) + txHash := BinarySha256(tx) blockTxsMap[string(txHash)] = struct{}{} } // Next, filter all txs from mem.txs that are in blockTxsMap txs := []Tx{} for _, tx := range mem.txs { - txHash := BinaryHash(tx) + txHash := BinarySha256(tx) if _, ok := blockTxsMap[string(txHash)]; ok { continue } else { diff --git a/mempool/reactor.go b/mempool/reactor.go index 05d7278d..7a2b620c 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -3,7 +3,6 @@ package mempool import ( "bytes" "fmt" - "io" "sync/atomic" . "github.com/tendermint/tendermint/binary" @@ -119,12 +118,10 @@ const ( // TODO: check for unnecessary extra bytes at the end. func decodeMessage(bz []byte) (msgType byte, msg interface{}) { n, err := new(int64), new(error) - // log.Debug("decoding msg bytes: %X", bz) msgType = bz[0] switch msgType { case msgTypeTx: - msg = readTxMessage(bytes.NewReader(bz[1:]), n, err) - // case ...: + msg = ReadBinary(&TxMessage{}, bytes.NewReader(bz[1:]), n, err) default: msg = nil } @@ -137,17 +134,7 @@ type TxMessage struct { Tx Tx } -func readTxMessage(r io.Reader, n *int64, err *error) *TxMessage { - return &TxMessage{ - Tx: ReadTx(r, n, err), - } -} - -func (m *TxMessage) WriteTo(w io.Writer) (n int64, err error) { - WriteByte(w, msgTypeTx, &n, &err) - WriteBinary(w, m.Tx, &n, &err) - return -} +func (m *TxMessage) TypeByte() byte { return msgTypeTx } func (m *TxMessage) String() string { return fmt.Sprintf("[TxMessage %v]", m.Tx) diff --git a/merkle/iavl_node.go b/merkle/iavl_node.go index dad7d38c..feece084 100644 --- a/merkle/iavl_node.go +++ b/merkle/iavl_node.go @@ -253,8 +253,8 @@ func (node *IAVLNode) remove(t *IAVLTree, key interface{}) ( // NOTE: sets hashes recursively func (node *IAVLNode) writeToCountHashes(t *IAVLTree, w io.Writer) (n int64, hashCount uint64, err error) { // height & size & key - WriteUInt8(w, node.height, &n, &err) - WriteUInt64(w, node.size, &n, &err) + WriteUInt8(node.height, w, &n, &err) + WriteUInt64(node.size, w, &n, &err) t.keyCodec.Encode(node.key, w, &n, &err) if err != nil { return @@ -273,7 +273,7 @@ func (node *IAVLNode) writeToCountHashes(t *IAVLTree, w io.Writer) (n int64, has if node.leftHash == nil { panic("node.leftHash was nil in save") } - WriteByteSlice(w, node.leftHash, &n, &err) + WriteByteSlice(node.leftHash, w, &n, &err) // right if node.rightNode != nil { rightHash, rightCount := node.rightNode.hashWithCount(t) @@ -283,7 +283,7 @@ func (node *IAVLNode) writeToCountHashes(t *IAVLTree, w io.Writer) (n int64, has if node.rightHash == nil { panic("node.rightHash was nil in save") } - WriteByteSlice(w, node.rightHash, &n, &err) + WriteByteSlice(node.rightHash, w, &n, &err) } return } diff --git a/merkle/simple_tree.go b/merkle/simple_tree.go index b3a6331c..5433dbfd 100644 --- a/merkle/simple_tree.go +++ b/merkle/simple_tree.go @@ -35,8 +35,8 @@ func HashFromTwoHashes(left []byte, right []byte) []byte { var n int64 var err error var hasher = sha256.New() - WriteByteSlice(hasher, left, &n, &err) - WriteByteSlice(hasher, right, &n, &err) + WriteByteSlice(left, hasher, &n, &err) + WriteByteSlice(right, hasher, &n, &err) if err != nil { panic(err) } @@ -58,16 +58,15 @@ func HashFromHashes(hashes [][]byte) []byte { } // Convenience for HashFromHashes. -func HashFromBinaries(items []Binary) []byte { +func HashFromBinaries(items []interface{}) []byte { hashes := [][]byte{} for _, item := range items { - hasher := sha256.New() - _, err := item.WriteTo(hasher) - if err != nil { + hasher, n, err := sha256.New(), new(int64), new(error) + WriteBinary(item, hasher, n, err) + if *err != nil { panic(err) } - hash := hasher.Sum(nil) - hashes = append(hashes, hash) + hashes = append(hashes, hasher.Sum(nil)) } return HashFromHashes(hashes) } diff --git a/p2p/connection.go b/p2p/connection.go index dfaf0e72..925babd5 100644 --- a/p2p/connection.go +++ b/p2p/connection.go @@ -17,7 +17,7 @@ import ( ) const ( - numBatchPackets = 10 + numBatchMsgPackets = 10 minReadBufferSize = 1024 minWriteBufferSize = 1024 flushThrottleMS = 50 @@ -35,7 +35,7 @@ type errorCbFunc func(interface{}) /* A MConnection wraps a network connection and handles buffering and multiplexing. -Binary messages are sent with ".Send(channelId, msg)". + inTotal { + return ErrTxInsufficientFunds } - // Good! - accDet.Balance -= stx.Amount - toAccDet.Balance += stx.Amount - s.SetAccountDetail(accDet) - s.SetAccountDetail(toAccDet) + fee := inTotal - outTotal + fees += fee + + // Good! Adjust accounts + s.AdjustByInputs(accounts, tx.Inputs) + s.AdjustByOutputs(accounts, tx.Outputs) + s.SetAccounts(accounts) return nil - //case *NameTx + case *BondTx: - //btx := tx.(*BondTx) - // Account must be nominal - if accDet.Status != AccountStatusNominal { - return ErrStateInvalidAccountState + tx := tx_.(*BondTx) + accounts, err := s.GetOrMakeAccounts(tx.Inputs, tx.UnbondTo) + if err != nil { + return err } - // Check account balance - if accDet.Balance < minBondAmount { - return ErrStateInsufficientFunds + signBytes := SignBytes(tx) + inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs) + if err != nil { + return err } - // Good! - accDet.Status = AccountStatusBonded - s.SetAccountDetail(accDet) + if err := tx.PubKey.ValidateBasic(); err != nil { + return err + } + outTotal, err := s.ValidateOutputs(tx.UnbondTo) + if err != nil { + return err + } + if outTotal > inTotal { + return ErrTxInsufficientFunds + } + fee := inTotal - outTotal + fees += fee + + // Good! Adjust accounts + s.AdjustByInputs(accounts, tx.Inputs) + s.SetAccounts(accounts) + // Add ValidatorInfo + updated := s.SetValidatorInfo(&ValidatorInfo{ + Address: tx.PubKey.Address(), + PubKey: tx.PubKey, + UnbondTo: tx.UnbondTo, + FirstBondHeight: s.LastBlockHeight + 1, + }) + if !updated { + panic("Failed to add validator info") + } + // Add Validator added := s.BondedValidators.Add(&Validator{ - Account: accDet.Account, - BondHeight: s.LastBlockHeight, - VotingPower: accDet.Balance, + Address: tx.PubKey.Address(), + PubKey: tx.PubKey, + BondHeight: s.LastBlockHeight + 1, + VotingPower: inTotal, Accum: 0, }) if !added { panic("Failed to add validator") } return nil + case *UnbondTx: - //utx := tx.(*UnbondTx) - // Account must be bonded. - if accDet.Status != AccountStatusBonded { - return ErrStateInvalidAccountState + tx := tx_.(*UnbondTx) + + // The validator must be active + _, val := s.BondedValidators.GetByAddress(tx.Address) + if val == nil { + return ErrTxInvalidAddress } + + // Verify the signature + signBytes := SignBytes(tx) + if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + return ErrTxInvalidSignature + } + + // tx.Height must be greater than val.LastCommitHeight + if tx.Height < val.LastCommitHeight { + return errors.New("Invalid bond height") + } + // Good! - s.unbondValidator(accDet.Id, accDet) - s.SetAccountDetail(accDet) + s.unbondValidator(val) return nil + case *DupeoutTx: - { - // NOTE: accDet is the one who created this transaction. - // Subtract any fees, save, and forget. - s.SetAccountDetail(accDet) - accDet = nil - } - dtx := tx.(*DupeoutTx) + tx := tx_.(*DupeoutTx) + // Verify the signatures - if dtx.VoteA.SignerId != dtx.VoteB.SignerId { - return ErrStateInvalidSignature - } - accused := s.GetAccountDetail(dtx.VoteA.SignerId) - if !accused.Verify(&dtx.VoteA) || !accused.Verify(&dtx.VoteB) { - return ErrStateInvalidSignature + _, accused := s.BondedValidators.GetByAddress(tx.Address) + voteASignBytes := SignBytes(&tx.VoteA) + voteBSignBytes := SignBytes(&tx.VoteB) + if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) || + !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) { + return ErrTxInvalidSignature } + // Verify equivocation - if dtx.VoteA.Height != dtx.VoteB.Height { - return errors.New("DupeoutTx height must be the same.") + // TODO: in the future, just require one vote from a previous height that + // doesn't exist on this chain. + if tx.VoteA.Height != tx.VoteB.Height { + return errors.New("DupeoutTx heights don't match") } - if dtx.VoteA.Type == VoteTypeCommit && dtx.VoteA.Round < dtx.VoteB.Round { + if tx.VoteA.Type == VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round { // Check special case. // Validators should not sign another vote after committing. } else { - if dtx.VoteA.Round != dtx.VoteB.Round { + if tx.VoteA.Round != tx.VoteB.Round { return errors.New("DupeoutTx rounds don't match") } - if dtx.VoteA.Type != dtx.VoteB.Type { + if tx.VoteA.Type != tx.VoteB.Type { return errors.New("DupeoutTx types don't match") } - if bytes.Equal(dtx.VoteA.BlockHash, dtx.VoteB.BlockHash) { - return errors.New("DupeoutTx blockhash shouldn't match") + if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { + return errors.New("DupeoutTx blockhashes shouldn't match") } } + // Good! (Bad validator!) - if accused.Status == AccountStatusBonded { - _, removed := s.BondedValidators.Remove(accused.Id) - if !removed { - panic("Failed to remove accused validator") - } - } else if accused.Status == AccountStatusUnbonding { - _, removed := s.UnbondingValidators.Remove(accused.Id) - if !removed { - panic("Failed to remove accused validator") - } - } else { - panic("Couldn't find accused validator") - } - accused.Status = AccountStatusDupedOut - updated := s.SetAccountDetail(accused) - if !updated { - panic("Failed to update accused validator account") - } + s.destroyValidator(accused) return nil + default: panic("Unknown Tx type") } } -// accDet optional -func (s *State) unbondValidator(accountId uint64, accDet *AccountDetail) { - if accDet == nil { - accDet = s.GetAccountDetail(accountId) - } - accDet.Status = AccountStatusUnbonding - s.SetAccountDetail(accDet) - val, removed := s.BondedValidators.Remove(accDet.Id) +func (s *State) unbondValidator(val *Validator) { + // Move validator to UnbondingValidators + val, removed := s.BondedValidators.Remove(val.Address) if !removed { - panic("Failed to remove validator") + panic("Couldn't remove validator for unbonding") } val.UnbondHeight = s.LastBlockHeight added := s.UnbondingValidators.Add(val) if !added { - panic("Failed to add validator") + panic("Couldn't add validator for unbonding") } } -func (s *State) releaseValidator(accountId uint64) { - accDet := s.GetAccountDetail(accountId) - if accDet.Status != AccountStatusUnbonding { - panic("Cannot release validator") +func (s *State) releaseValidator(val *Validator) { + // Update validatorInfo + valInfo := s.GetValidatorInfo(val.Address) + if valInfo == nil { + panic("Couldn't find validatorInfo for release") } - accDet.Status = AccountStatusNominal - // TODO: move balance to designated address, UnbondTo. - s.SetAccountDetail(accDet) - _, removed := s.UnbondingValidators.Remove(accountId) + valInfo.ReleasedHeight = s.LastBlockHeight + 1 + s.SetValidatorInfo(valInfo) + + // Send coins back to UnbondTo outputs + accounts, err := s.GetOrMakeAccounts(nil, valInfo.UnbondTo) + if err != nil { + panic("Couldn't get or make unbondTo accounts") + } + s.AdjustByOutputs(accounts, valInfo.UnbondTo) + s.SetAccounts(accounts) + + // Remove validator from UnbondingValidators + _, removed := s.UnbondingValidators.Remove(val.Address) if !removed { - panic("Couldn't release validator") + panic("Couldn't remove validator for release") } } +func (s *State) destroyValidator(val *Validator) { + // Update validatorInfo + valInfo := s.GetValidatorInfo(val.Address) + if valInfo == nil { + panic("Couldn't find validatorInfo for release") + } + valInfo.DestroyedHeight = s.LastBlockHeight + 1 + valInfo.DestroyedAmount = val.VotingPower + s.SetValidatorInfo(valInfo) + + // Remove validator + _, removed := s.BondedValidators.Remove(val.Address) + if !removed { + _, removed := s.UnbondingValidators.Remove(val.Address) + if !removed { + panic("Couldn't remove validator for destruction") + } + } + +} + // "checkStateHash": If false, instead of checking the resulting // state.Hash() against block.StateHash, it *sets* the block.StateHash. // (used for constructing a new proposal) @@ -308,23 +431,18 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS } var sumVotingPower uint64 s.BondedValidators.Iterate(func(index uint, val *Validator) bool { - rsig := block.Validation.Commits[index] - if rsig.IsZero() { + commit := block.Validation.Commits[index] + if commit.IsZero() { return false } else { - if rsig.SignerId != val.Id { - err = errors.New("Invalid validation order") - return true - } vote := &Vote{ Height: block.Height - 1, - Round: rsig.Round, + Round: commit.Round, Type: VoteTypeCommit, BlockHash: block.LastBlockHash, BlockParts: block.LastBlockParts, - Signature: rsig.Signature, } - if val.Verify(vote) { + if val.PubKey.VerifyBytes(SignBytes(vote), commit.Signature) { sumVotingPower += val.VotingPower return false } else { @@ -351,10 +469,13 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS } // Update Validator.LastCommitHeight as necessary. - for _, rsig := range block.Validation.Commits { - _, val := s.BondedValidators.GetById(rsig.SignerId) + for i, commit := range block.Validation.Commits { + if commit.IsZero() { + continue + } + _, val := s.BondedValidators.GetByIndex(uint(i)) if val == nil { - return ErrStateInvalidSignature + return ErrTxInvalidSignature } val.LastCommitHeight = block.Height - 1 updated := s.BondedValidators.Update(val) @@ -373,7 +494,7 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS return false }) for _, val := range toRelease { - s.releaseValidator(val.Id) + s.releaseValidator(val) } // If any validators haven't signed in a while, @@ -386,7 +507,7 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS return false }) for _, val := range toTimeout { - s.unbondValidator(val.Id, nil) + s.unbondValidator(val) } // Increment validator AccumPowers @@ -415,21 +536,39 @@ func (s *State) AppendBlock(block *Block, blockPartsHeader PartSetHeader, checkS return nil } -// The returned AccountDetail is a copy, so mutating it +// The returned Account is a copy, so mutating it // has no side effects. -func (s *State) GetAccountDetail(accountId uint64) *AccountDetail { - _, accDet := s.accountDetails.Get(accountId) - if accDet == nil { +func (s *State) GetAccount(address []byte) *Account { + _, account := s.accounts.Get(address) + if account == nil { return nil } - return accDet.(*AccountDetail).Copy() + return account.(*Account).Copy() +} + +// The accounts are copied before setting, so mutating it +// afterwards has no side effects. +func (s *State) SetAccounts(accounts map[string]*Account) { + for _, account := range accounts { + s.accounts.Set(account.Address, account.Copy()) + } +} + +// The returned ValidatorInfo is a copy, so mutating it +// has no side effects. +func (s *State) GetValidatorInfo(address []byte) *ValidatorInfo { + _, valInfo := s.validatorInfos.Get(address) + if valInfo == nil { + return nil + } + return valInfo.(*ValidatorInfo).Copy() } // Returns false if new, true if updated. -// The accDet is copied before setting, so mutating it +// The valInfo is copied before setting, so mutating it // afterwards has no side effects. -func (s *State) SetAccountDetail(accDet *AccountDetail) (updated bool) { - return s.accountDetails.Set(accDet.Id, accDet.Copy()) +func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) { + return s.validatorInfos.Set(valInfo.Address, valInfo.Copy()) } // Returns a hash that represents the state data, @@ -438,7 +577,7 @@ func (s *State) Hash() []byte { hashables := []merkle.Hashable{ s.BondedValidators, s.UnbondingValidators, - s.accountDetails, + s.accounts, } return merkle.HashFromHashables(hashables) } diff --git a/state/validator.go b/state/validator.go index c49aa097..0ba00f82 100644 --- a/state/validator.go +++ b/state/validator.go @@ -1,34 +1,61 @@ package state import ( + "bytes" "fmt" "io" + . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" + . "github.com/tendermint/tendermint/blocks" ) -// Holds state for a Validator at a given height+round. -// Meant to be discarded every round of the consensus protocol. -// TODO consider moving this to another common types package. -type Validator struct { - Account - BondHeight uint32 - UnbondHeight uint32 - LastCommitHeight uint32 - VotingPower uint64 - Accum int64 +// Persistent static data for each Validator +type ValidatorInfo struct { + Address []byte + PubKey PubKeyEd25519 + UnbondTo []*TxOutput + FirstBondHeight uint + + // If destroyed: + DestroyedHeight uint + DestroyedAmount uint64 + + // If released: + ReleasedHeight uint } -// Used to persist the state of ConsensusStateControl. -func ReadValidator(r io.Reader, n *int64, err *error) *Validator { - return &Validator{ - Account: ReadAccount(r, n, err), - BondHeight: ReadUInt32(r, n, err), - UnbondHeight: ReadUInt32(r, n, err), - LastCommitHeight: ReadUInt32(r, n, err), - VotingPower: ReadUInt64(r, n, err), - Accum: ReadInt64(r, n, err), - } +func (valInfo *ValidatorInfo) Copy() *ValidatorInfo { + valInfoCopy := *valInfo + return &valInfoCopy +} + +func ValidatorInfoEncoder(o interface{}, w io.Writer, n *int64, err *error) { + WriteBinary(o.(*ValidatorInfo), w, n, err) +} + +func ValidatorInfoDecoder(r io.Reader, n *int64, err *error) interface{} { + return ReadBinary(&ValidatorInfo{}, r, n, err) +} + +var ValidatorInfoCodec = Codec{ + Encode: ValidatorInfoEncoder, + Decode: ValidatorInfoDecoder, +} + +//----------------------------------------------------------------------------- + +// Volatile state for each Validator +// Also persisted with the state, but fields change +// every height|round so they don't go in merkle.Tree +type Validator struct { + Address []byte + PubKey PubKeyEd25519 + BondHeight uint + UnbondHeight uint + LastCommitHeight uint + VotingPower uint64 + Accum int64 } // Creates a new copy of the validator so we can mutate accum. @@ -37,17 +64,6 @@ func (v *Validator) Copy() *Validator { return &vCopy } -// Used to persist the state of ConsensusStateControl. -func (v *Validator) WriteTo(w io.Writer) (n int64, err error) { - WriteBinary(w, v.Account, &n, &err) - WriteUInt32(w, v.BondHeight, &n, &err) - WriteUInt32(w, v.UnbondHeight, &n, &err) - WriteUInt32(w, v.LastCommitHeight, &n, &err) - WriteUInt64(w, v.VotingPower, &n, &err) - WriteInt64(w, v.Accum, &n, &err) - return -} - // Returns the one with higher Accum. func (v *Validator) CompareAccum(other *Validator) *Validator { if v == nil { @@ -58,9 +74,9 @@ func (v *Validator) CompareAccum(other *Validator) *Validator { } else if v.Accum < other.Accum { return other } else { - if v.Id < other.Id { + if bytes.Compare(v.Address, other.Address) < 0 { return v - } else if v.Id > other.Id { + } else if bytes.Compare(v.Address, other.Address) > 0 { return other } else { panic("Cannot compare identical validators") @@ -69,8 +85,9 @@ func (v *Validator) CompareAccum(other *Validator) *Validator { } func (v *Validator) String() string { - return fmt.Sprintf("Validator{%v %v-%v-%v VP:%v A:%v}", - v.Account, + return fmt.Sprintf("Validator{%X %v %v-%v-%v VP:%v A:%v}", + v.Address, + v.PubKey, v.BondHeight, v.LastCommitHeight, v.UnbondHeight, @@ -79,7 +96,7 @@ func (v *Validator) String() string { } func (v *Validator) Hash() []byte { - return BinaryHash(v) + return BinarySha256(v) } //------------------------------------- @@ -89,11 +106,11 @@ var ValidatorCodec = validatorCodec{} type validatorCodec struct{} func (vc validatorCodec) Encode(o interface{}, w io.Writer, n *int64, err *error) { - WriteBinary(w, o.(*Validator), n, err) + WriteBinary(o.(*Validator), w, n, err) } func (vc validatorCodec) Decode(r io.Reader, n *int64, err *error) interface{} { - return ReadValidator(r, n, err) + return ReadBinary(&Validator{}, r, n, err) } func (vc validatorCodec) Compare(o1 interface{}, o2 interface{}) int { diff --git a/state/validator_set.go b/state/validator_set.go index 9bb1ad94..b0e4b898 100644 --- a/state/validator_set.go +++ b/state/validator_set.go @@ -1,12 +1,11 @@ package state import ( + "bytes" "fmt" - "io" "sort" "strings" - . "github.com/tendermint/tendermint/binary" "github.com/tendermint/tendermint/merkle" ) @@ -20,7 +19,7 @@ func (vs ValidatorSlice) Len() int { } func (vs ValidatorSlice) Less(i, j int) bool { - return vs[i].Id < vs[j].Id + return bytes.Compare(vs[i].Address, vs[j].Address) == -1 } func (vs ValidatorSlice) Swap(i, j int) { @@ -31,10 +30,17 @@ func (vs ValidatorSlice) Swap(i, j int) { //------------------------------------- -// Not goroutine-safe. -// TODO: consider validator Accum overflow? +// ValidatorSet represent a set of *Validator at a given height. +// The validators can be fetched by address or index. +// The index is in order of .Address, so the index are the same +// for all rounds of a given blockchain height. +// On the other hand, the .AccumPower of each validator and +// the designated .Proposer() of a set changes every round, +// upon calling .IncrementAccum(). +// NOTE: Not goroutine-safe. +// NOTE: All get/set to validators should copy the value for safety. +// TODO: consider validator Accum overflow // TODO: replace validators []*Validator with github.com/jaekwon/go-ibbs? -// NOTE: all get/set to validators should copy the value for safety. type ValidatorSet struct { validators []*Validator @@ -54,169 +60,149 @@ func NewValidatorSet(vals []*Validator) *ValidatorSet { } } -func ReadValidatorSet(r io.Reader, n *int64, err *error) *ValidatorSet { - size := ReadUVarInt(r, n, err) - validators := []*Validator{} - for i := uint(0); i < size; i++ { - validator := ReadValidator(r, n, err) - validators = append(validators, validator) - } - sort.Sort(ValidatorSlice(validators)) - return NewValidatorSet(validators) -} - -func (vset *ValidatorSet) WriteTo(w io.Writer) (n int64, err error) { - WriteUVarInt(w, uint(len(vset.validators)), &n, &err) - vset.Iterate(func(index uint, val *Validator) bool { - WriteBinary(w, val, &n, &err) - return false - }) - return -} - -func (vset *ValidatorSet) IncrementAccum() { +func (valSet *ValidatorSet) IncrementAccum() { // Decrement from previous proposer - oldProposer := vset.Proposer() - oldProposer.Accum -= int64(vset.TotalVotingPower()) - vset.Update(oldProposer) + oldProposer := valSet.Proposer() + oldProposer.Accum -= int64(valSet.TotalVotingPower()) + valSet.Update(oldProposer) var newProposer *Validator // Increment accum and find new proposer // NOTE: updates validators in place. - for _, val := range vset.validators { + for _, val := range valSet.validators { val.Accum += int64(val.VotingPower) newProposer = newProposer.CompareAccum(val) } - vset.proposer = newProposer + valSet.proposer = newProposer } -func (vset *ValidatorSet) Copy() *ValidatorSet { - validators := make([]*Validator, len(vset.validators)) - for i, val := range vset.validators { +func (valSet *ValidatorSet) Copy() *ValidatorSet { + validators := make([]*Validator, len(valSet.validators)) + for i, val := range valSet.validators { // NOTE: must copy, since IncrementAccum updates in place. validators[i] = val.Copy() } return &ValidatorSet{ validators: validators, - proposer: vset.proposer, - totalVotingPower: vset.totalVotingPower, + proposer: valSet.proposer, + totalVotingPower: valSet.totalVotingPower, } } -func (vset *ValidatorSet) HasId(id uint64) bool { - idx := sort.Search(len(vset.validators), func(i int) bool { - return id <= vset.validators[i].Id +func (valSet *ValidatorSet) HasAddress(address []byte) bool { + idx := sort.Search(len(valSet.validators), func(i int) bool { + return bytes.Compare(address, valSet.validators[i].Address) <= 0 }) - return idx != len(vset.validators) && vset.validators[idx].Id == id + return idx != len(valSet.validators) && bytes.Compare(valSet.validators[idx].Address, address) == 0 } -func (vset *ValidatorSet) GetById(id uint64) (index uint, val *Validator) { - idx := sort.Search(len(vset.validators), func(i int) bool { - return id <= vset.validators[i].Id +func (valSet *ValidatorSet) GetByAddress(address []byte) (index uint, val *Validator) { + idx := sort.Search(len(valSet.validators), func(i int) bool { + return bytes.Compare(address, valSet.validators[i].Address) <= 0 }) - if idx != len(vset.validators) && vset.validators[idx].Id == id { - return uint(idx), vset.validators[idx].Copy() + if idx != len(valSet.validators) && bytes.Compare(valSet.validators[idx].Address, address) == 0 { + return uint(idx), valSet.validators[idx].Copy() } else { return 0, nil } } -func (vset *ValidatorSet) GetByIndex(index uint) (id uint64, val *Validator) { - val = vset.validators[index] - return val.Id, val.Copy() +func (valSet *ValidatorSet) GetByIndex(index uint) (address []byte, val *Validator) { + val = valSet.validators[index] + return val.Address, val.Copy() } -func (vset *ValidatorSet) Size() uint { - return uint(len(vset.validators)) +func (valSet *ValidatorSet) Size() uint { + return uint(len(valSet.validators)) } -func (vset *ValidatorSet) TotalVotingPower() uint64 { - if vset.totalVotingPower == 0 { - for _, val := range vset.validators { - vset.totalVotingPower += val.VotingPower +func (valSet *ValidatorSet) TotalVotingPower() uint64 { + if valSet.totalVotingPower == 0 { + for _, val := range valSet.validators { + valSet.totalVotingPower += val.VotingPower } } - return vset.totalVotingPower + return valSet.totalVotingPower } -func (vset *ValidatorSet) Proposer() (proposer *Validator) { - if vset.proposer == nil { - for _, val := range vset.validators { - vset.proposer = vset.proposer.CompareAccum(val) +func (valSet *ValidatorSet) Proposer() (proposer *Validator) { + if valSet.proposer == nil { + for _, val := range valSet.validators { + valSet.proposer = valSet.proposer.CompareAccum(val) } } - return vset.proposer.Copy() + return valSet.proposer.Copy() } -func (vset *ValidatorSet) Hash() []byte { - if len(vset.validators) == 0 { +func (valSet *ValidatorSet) Hash() []byte { + if len(valSet.validators) == 0 { return nil } - hashables := make([]merkle.Hashable, len(vset.validators)) - for i, val := range vset.validators { + hashables := make([]merkle.Hashable, len(valSet.validators)) + for i, val := range valSet.validators { hashables[i] = val } return merkle.HashFromHashables(hashables) } -func (vset *ValidatorSet) Add(val *Validator) (added bool) { +func (valSet *ValidatorSet) Add(val *Validator) (added bool) { val = val.Copy() - idx := sort.Search(len(vset.validators), func(i int) bool { - return val.Id <= vset.validators[i].Id + idx := sort.Search(len(valSet.validators), func(i int) bool { + return bytes.Compare(val.Address, valSet.validators[i].Address) <= 0 }) - if idx == len(vset.validators) { - vset.validators = append(vset.validators, val) + if idx == len(valSet.validators) { + valSet.validators = append(valSet.validators, val) // Invalidate cache - vset.proposer = nil - vset.totalVotingPower = 0 + valSet.proposer = nil + valSet.totalVotingPower = 0 return true - } else if vset.validators[idx].Id == val.Id { + } else if bytes.Compare(valSet.validators[idx].Address, val.Address) == 0 { return false } else { - newValidators := append(vset.validators[:idx], val) - newValidators = append(newValidators, vset.validators[idx:]...) - vset.validators = newValidators + newValidators := append(valSet.validators[:idx], val) + newValidators = append(newValidators, valSet.validators[idx:]...) + valSet.validators = newValidators // Invalidate cache - vset.proposer = nil - vset.totalVotingPower = 0 + valSet.proposer = nil + valSet.totalVotingPower = 0 return true } } -func (vset *ValidatorSet) Update(val *Validator) (updated bool) { - index, sameVal := vset.GetById(val.Id) +func (valSet *ValidatorSet) Update(val *Validator) (updated bool) { + index, sameVal := valSet.GetByAddress(val.Address) if sameVal == nil { return false } else { - vset.validators[index] = val.Copy() + valSet.validators[index] = val.Copy() // Invalidate cache - vset.proposer = nil - vset.totalVotingPower = 0 + valSet.proposer = nil + valSet.totalVotingPower = 0 return true } } -func (vset *ValidatorSet) Remove(id uint64) (val *Validator, removed bool) { - idx := sort.Search(len(vset.validators), func(i int) bool { - return id <= vset.validators[i].Id +func (valSet *ValidatorSet) Remove(address []byte) (val *Validator, removed bool) { + idx := sort.Search(len(valSet.validators), func(i int) bool { + return bytes.Compare(address, valSet.validators[i].Address) <= 0 }) - if idx == len(vset.validators) || vset.validators[idx].Id != id { + if idx == len(valSet.validators) || bytes.Compare(valSet.validators[idx].Address, address) != 0 { return nil, false } else { - removedVal := vset.validators[idx] - newValidators := vset.validators[:idx] - if idx+1 < len(vset.validators) { - newValidators = append(newValidators, vset.validators[idx+1:]...) + removedVal := valSet.validators[idx] + newValidators := valSet.validators[:idx] + if idx+1 < len(valSet.validators) { + newValidators = append(newValidators, valSet.validators[idx+1:]...) } - vset.validators = newValidators + valSet.validators = newValidators // Invalidate cache - vset.proposer = nil - vset.totalVotingPower = 0 + valSet.proposer = nil + valSet.totalVotingPower = 0 return removedVal, true } } -func (vset *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) { - for i, val := range vset.validators { +func (valSet *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) { + for i, val := range valSet.validators { stop := fn(uint(i), val.Copy()) if stop { break @@ -224,13 +210,13 @@ func (vset *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) { } } -func (vset *ValidatorSet) String() string { - return vset.StringWithIndent("") +func (valSet *ValidatorSet) String() string { + return valSet.StringWithIndent("") } -func (vset *ValidatorSet) StringWithIndent(indent string) string { +func (valSet *ValidatorSet) StringWithIndent(indent string) string { valStrings := []string{} - vset.Iterate(func(index uint, val *Validator) bool { + valSet.Iterate(func(index uint, val *Validator) bool { valStrings = append(valStrings, val.String()) return false }) @@ -239,7 +225,7 @@ func (vset *ValidatorSet) StringWithIndent(indent string) string { %s Validators: %s %v %s}`, - indent, vset.Proposer().String(), + indent, valSet.Proposer().String(), indent, indent, strings.Join(valStrings, "\n"+indent+" "), indent) diff --git a/wallet/priv_account.go b/wallet/priv_account.go new file mode 100644 index 00000000..db95e725 --- /dev/null +++ b/wallet/priv_account.go @@ -0,0 +1,28 @@ +package wallet + +import ( + "github.com/tendermint/go-ed25519" + + . "github.com/tendermint/tendermint/account" + . "github.com/tendermint/tendermint/common" +) + +type PrivAccount struct { + PubKey PubKey + PrivKey PrivKey +} + +// Generates a new account with private key. +func GenPrivAccount() *PrivAccount { + privKey := CRandBytes(32) + pubKey := ed25519.MakePubKey(privKey) + return &PrivAccount{ + PubKeyEd25519{ + PubKey: pubKey, + }, + PrivKeyEd25519{ + PubKey: pubKey, + PrivKey: privKey, + }, + } +}