mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-19 14:34:17 +00:00
saving development state...
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"io"
|
||||
)
|
||||
|
||||
type AccountId struct {
|
||||
Type Byte
|
||||
Number UInt64
|
||||
PubKey ByteSlice
|
||||
}
|
||||
|
||||
const (
|
||||
ACCOUNT_TYPE_NUMBER = Byte(0x01)
|
||||
ACCOUNT_TYPE_PUBKEY = Byte(0x02)
|
||||
ACCOUNT_TYPE_BOTH = Byte(0x03)
|
||||
)
|
||||
|
||||
func ReadAccountId(r io.Reader) AccountId {
|
||||
switch t := ReadByte(r); t {
|
||||
case ACCOUNT_TYPE_NUMBER:
|
||||
return AccountId{t, ReadUInt64(r), nil}
|
||||
case ACCOUNT_TYPE_PUBKEY:
|
||||
return AccountId{t, 0, ReadByteSlice(r)}
|
||||
case ACCOUNT_TYPE_BOTH:
|
||||
return AccountId{t, ReadUInt64(r), ReadByteSlice(r)}
|
||||
default:
|
||||
Panicf("Unknown AccountId type %x", t)
|
||||
return AccountId{}
|
||||
}
|
||||
}
|
||||
|
||||
func (self AccountId) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(self.Type, w, n, err)
|
||||
if self.Type == ACCOUNT_TYPE_NUMBER || self.Type == ACCOUNT_TYPE_BOTH {
|
||||
n, err = WriteTo(self.Number, w, n, err)
|
||||
}
|
||||
if self.Type == ACCOUNT_TYPE_PUBKEY || self.Type == ACCOUNT_TYPE_BOTH {
|
||||
n, err = WriteTo(self.PubKey, w, n, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func AccountNumber(n UInt64) AccountId {
|
||||
return AccountId{ACCOUNT_TYPE_NUMBER, n, nil}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// NOTE: consensus/Validator embeds this, so..
|
||||
type Account struct {
|
||||
Id UInt64 // Numeric id of account, incrementing.
|
||||
PubKey ByteSlice
|
||||
}
|
||||
|
||||
func (self *Account) Verify(msg ByteSlice, sig ByteSlice) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type PrivAccount struct {
|
||||
Account
|
||||
PrivKey ByteSlice
|
||||
}
|
||||
|
||||
func (self *PrivAccount) Sign(msg ByteSlice) Signature {
|
||||
return Signature{}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
Signature message wire format:
|
||||
|
||||
|A...|SSS...|
|
||||
|
||||
A account number, varint encoded (1+ bytes)
|
||||
S signature of all prior bytes (32 bytes)
|
||||
|
||||
It usually follows the message to be signed.
|
||||
|
||||
*/
|
||||
|
||||
type Signature struct {
|
||||
SignerId UInt64
|
||||
Bytes ByteSlice
|
||||
}
|
||||
|
||||
func ReadSignature(r io.Reader) Signature {
|
||||
return Signature{
|
||||
SignerId: ReadUInt64(r),
|
||||
Bytes: ReadByteSlice(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (sig Signature) IsZero() bool {
|
||||
return len(sig.Bytes) == 0
|
||||
}
|
||||
|
||||
func (sig Signature) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(sig.SignerId, w, n, err)
|
||||
n, err = WriteTo(sig.Bytes, w, n, err)
|
||||
return
|
||||
}
|
||||
+41
-13
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
TODO: signing a bad checkpoint (block)
|
||||
*/
|
||||
|
||||
type Adjustment interface {
|
||||
Type() Byte
|
||||
Binary
|
||||
@@ -33,7 +32,7 @@ func ReadAdjustment(r io.Reader) Adjustment {
|
||||
case ADJ_TYPE_BOND:
|
||||
return &Bond{
|
||||
Fee: ReadUInt64(r),
|
||||
UnbondTo: ReadAccountId(r),
|
||||
UnbondTo: ReadUInt64(r),
|
||||
Amount: ReadUInt64(r),
|
||||
Signature: ReadSignature(r),
|
||||
}
|
||||
@@ -45,13 +44,13 @@ func ReadAdjustment(r io.Reader) Adjustment {
|
||||
}
|
||||
case ADJ_TYPE_TIMEOUT:
|
||||
return &Timeout{
|
||||
Account: ReadAccountId(r),
|
||||
Account: ReadUInt64(r),
|
||||
Penalty: ReadUInt64(r),
|
||||
}
|
||||
case ADJ_TYPE_DUPEOUT:
|
||||
return &Dupeout{
|
||||
VoteA: ReadVote(r),
|
||||
VoteB: ReadVote(r),
|
||||
VoteA: ReadBlockVote(r),
|
||||
VoteB: ReadBlockVote(r),
|
||||
}
|
||||
default:
|
||||
Panicf("Unknown Adjustment type %x", t)
|
||||
@@ -59,11 +58,12 @@ func ReadAdjustment(r io.Reader) Adjustment {
|
||||
}
|
||||
}
|
||||
|
||||
/* Bond < Adjustment */
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/* Bond < Adjustment */
|
||||
type Bond struct {
|
||||
Fee UInt64
|
||||
UnbondTo AccountId
|
||||
UnbondTo UInt64
|
||||
Amount UInt64
|
||||
Signature
|
||||
}
|
||||
@@ -81,8 +81,9 @@ func (self *Bond) WriteTo(w io.Writer) (n int64, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
/* Unbond < Adjustment */
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/* Unbond < Adjustment */
|
||||
type Unbond struct {
|
||||
Fee UInt64
|
||||
Amount UInt64
|
||||
@@ -101,10 +102,11 @@ func (self *Unbond) WriteTo(w io.Writer) (n int64, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
/* Timeout < Adjustment */
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/* Timeout < Adjustment */
|
||||
type Timeout struct {
|
||||
Account AccountId
|
||||
Account UInt64
|
||||
Penalty UInt64
|
||||
}
|
||||
|
||||
@@ -119,11 +121,37 @@ func (self *Timeout) WriteTo(w io.Writer) (n int64, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
/* Dupeout < Adjustment */
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
The full vote structure is only needed when presented as evidence.
|
||||
Typically only the signature is passed around, as the hash & height are implied.
|
||||
*/
|
||||
type BlockVote struct {
|
||||
Height UInt64
|
||||
BlockHash ByteSlice
|
||||
Signature
|
||||
}
|
||||
|
||||
func ReadBlockVote(r io.Reader) BlockVote {
|
||||
return BlockVote{
|
||||
Height: ReadUInt64(r),
|
||||
BlockHash: ReadByteSlice(r),
|
||||
Signature: ReadSignature(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (self BlockVote) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(self.Height, w, n, err)
|
||||
n, err = WriteTo(self.BlockHash, w, n, err)
|
||||
n, err = WriteTo(self.Signature, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
/* Dupeout < Adjustment */
|
||||
type Dupeout struct {
|
||||
VoteA Vote
|
||||
VoteB Vote
|
||||
VoteA BlockVote
|
||||
VoteB BlockVote
|
||||
}
|
||||
|
||||
func (self *Dupeout) Type() Byte {
|
||||
|
||||
+224
-43
@@ -1,16 +1,35 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
"github.com/tendermint/tendermint/merkle"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/merkle"
|
||||
)
|
||||
|
||||
/* Block */
|
||||
const (
|
||||
defaultBlockPartSizeBytes = 4096
|
||||
)
|
||||
|
||||
func CalcBlockURI(height uint32, hash []byte) string {
|
||||
return fmt.Sprintf("%v://block/%v#%X",
|
||||
config.Config.Network,
|
||||
height,
|
||||
hash,
|
||||
)
|
||||
}
|
||||
|
||||
type Block struct {
|
||||
Header
|
||||
Validation
|
||||
Txs
|
||||
|
||||
// Volatile
|
||||
hash []byte
|
||||
}
|
||||
|
||||
func ReadBlock(r io.Reader) *Block {
|
||||
@@ -21,60 +40,200 @@ func ReadBlock(r io.Reader) *Block {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Block) Validate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *Block) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(&self.Header, w, n, err)
|
||||
n, err = WriteTo(&self.Validation, w, n, err)
|
||||
n, err = WriteTo(&self.Txs, w, n, err)
|
||||
func (b *Block) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(&b.Header, w, n, err)
|
||||
n, err = WriteTo(&b.Validation, w, n, err)
|
||||
n, err = WriteTo(&b.Txs, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
/* Block > Header */
|
||||
func (b *Block) ValidateBasic() error {
|
||||
// Basic validation that doesn't involve context.
|
||||
// XXX
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Block) URI() string {
|
||||
return CalcBlockURI(uint32(b.Height), b.Hash())
|
||||
}
|
||||
|
||||
func (b *Block) Hash() []byte {
|
||||
if b.hash != nil {
|
||||
return b.hash
|
||||
} else {
|
||||
hashes := []Binary{
|
||||
ByteSlice(b.Header.Hash()),
|
||||
ByteSlice(b.Validation.Hash()),
|
||||
ByteSlice(b.Txs.Hash()),
|
||||
}
|
||||
// Merkle hash from sub-hashes.
|
||||
return merkle.HashFromBinarySlice(hashes)
|
||||
}
|
||||
}
|
||||
|
||||
// The returns parts must be signed afterwards.
|
||||
func (b *Block) ToBlockParts() (parts []*BlockPart) {
|
||||
blockBytes := BinaryBytes(b)
|
||||
total := (len(blockBytes) + defaultBlockPartSizeBytes - 1) / defaultBlockPartSizeBytes
|
||||
for i := 0; i < total; i++ {
|
||||
start := defaultBlockPartSizeBytes * i
|
||||
end := MinInt(start+defaultBlockPartSizeBytes, len(blockBytes))
|
||||
partBytes := make([]byte, end-start)
|
||||
copy(partBytes, blockBytes[start:end]) // Do not ref the original byteslice.
|
||||
part := &BlockPart{
|
||||
Height: b.Height,
|
||||
Index: UInt16(i),
|
||||
Total: UInt16(total),
|
||||
Bytes: partBytes,
|
||||
Signature: Signature{}, // No signature.
|
||||
}
|
||||
parts = append(parts, part)
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
BlockPart represents a chunk of the bytes of a block.
|
||||
Each block is divided into fixed length chunks (e.g. 4Kb)
|
||||
for faster propagation across the gossip network.
|
||||
*/
|
||||
type BlockPart struct {
|
||||
Height UInt32
|
||||
Round UInt16 // Add Round? Well I need to know...
|
||||
Index UInt16
|
||||
Total UInt16
|
||||
Bytes ByteSlice
|
||||
Signature
|
||||
|
||||
// Volatile
|
||||
hash []byte
|
||||
}
|
||||
|
||||
func ReadBlockPart(r io.Reader) *BlockPart {
|
||||
return &BlockPart{
|
||||
Height: ReadUInt32(r),
|
||||
Round: ReadUInt16(r),
|
||||
Index: ReadUInt16(r),
|
||||
Total: ReadUInt16(r),
|
||||
Bytes: ReadByteSlice(r),
|
||||
Signature: ReadSignature(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (bp *BlockPart) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(&bp.Height, w, n, err)
|
||||
n, err = WriteTo(&bp.Round, w, n, err)
|
||||
n, err = WriteTo(&bp.Index, w, n, err)
|
||||
n, err = WriteTo(&bp.Total, w, n, err)
|
||||
n, err = WriteTo(&bp.Bytes, w, n, err)
|
||||
n, err = WriteTo(&bp.Signature, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (bp *BlockPart) URI() string {
|
||||
return fmt.Sprintf("%v://block/%v/%v[%v/%v]#%X\n",
|
||||
config.Config.Network,
|
||||
bp.Height,
|
||||
bp.Round,
|
||||
bp.Index,
|
||||
bp.Total,
|
||||
bp.BlockPartHash(),
|
||||
)
|
||||
}
|
||||
|
||||
func (bp *BlockPart) BlockPartHash() []byte {
|
||||
if bp.hash != nil {
|
||||
return bp.hash
|
||||
} else {
|
||||
hasher := sha256.New()
|
||||
hasher.Write(bp.Bytes)
|
||||
bp.hash = hasher.Sum(nil)
|
||||
return bp.hash
|
||||
}
|
||||
}
|
||||
|
||||
// Signs the URI, which includes all data and metadata.
|
||||
// XXX implement or change
|
||||
func (bp *BlockPart) Sign(acc *PrivAccount) {
|
||||
// TODO: populate Signature
|
||||
}
|
||||
|
||||
// XXX maybe change.
|
||||
func (bp *BlockPart) ValidateWithSigner(signer *Account) error {
|
||||
// TODO: Sanity check height, index, total, bytes, etc.
|
||||
if !signer.Verify([]byte(bp.URI()), bp.Signature.Bytes) {
|
||||
return ErrInvalidBlockPartSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/* Header is part of a Block */
|
||||
type Header struct {
|
||||
Name String
|
||||
Height UInt64
|
||||
Height UInt32
|
||||
Fees UInt64
|
||||
Time UInt64
|
||||
Time Time
|
||||
PrevHash ByteSlice
|
||||
ValidationHash ByteSlice
|
||||
TxsHash ByteSlice
|
||||
|
||||
// Volatile
|
||||
hash []byte
|
||||
}
|
||||
|
||||
func ReadHeader(r io.Reader) Header {
|
||||
return Header{
|
||||
Name: ReadString(r),
|
||||
Height: ReadUInt64(r),
|
||||
Height: ReadUInt32(r),
|
||||
Fees: ReadUInt64(r),
|
||||
Time: ReadUInt64(r),
|
||||
Time: ReadTime(r),
|
||||
PrevHash: ReadByteSlice(r),
|
||||
ValidationHash: ReadByteSlice(r),
|
||||
TxsHash: ReadByteSlice(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Header) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(self.Name, w, n, err)
|
||||
n, err = WriteTo(self.Height, w, n, err)
|
||||
n, err = WriteTo(self.Fees, w, n, err)
|
||||
n, err = WriteTo(self.Time, w, n, err)
|
||||
n, err = WriteTo(self.PrevHash, w, n, err)
|
||||
n, err = WriteTo(self.ValidationHash, w, n, err)
|
||||
n, err = WriteTo(self.TxsHash, w, n, err)
|
||||
func (h *Header) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(h.Name, w, n, err)
|
||||
n, err = WriteTo(h.Height, w, n, err)
|
||||
n, err = WriteTo(h.Fees, w, n, err)
|
||||
n, err = WriteTo(h.Time, w, n, err)
|
||||
n, err = WriteTo(h.PrevHash, w, n, err)
|
||||
n, err = WriteTo(h.ValidationHash, w, n, err)
|
||||
n, err = WriteTo(h.TxsHash, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
/* Block > Validation */
|
||||
func (h *Header) Hash() []byte {
|
||||
if h.hash != nil {
|
||||
return h.hash
|
||||
} else {
|
||||
hasher := sha256.New()
|
||||
_, err := h.WriteTo(hasher)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
h.hash = hasher.Sum(nil)
|
||||
return h.hash
|
||||
}
|
||||
}
|
||||
|
||||
/* Validation is part of a block */
|
||||
type Validation struct {
|
||||
Signatures []Signature
|
||||
Adjustments []Adjustment
|
||||
|
||||
// Volatile
|
||||
hash []byte
|
||||
}
|
||||
|
||||
func ReadValidation(r io.Reader) Validation {
|
||||
numSigs := int(ReadUInt64(r))
|
||||
numAdjs := int(ReadUInt64(r))
|
||||
numSigs := int(ReadUInt32(r))
|
||||
numAdjs := int(ReadUInt32(r))
|
||||
sigs := make([]Signature, 0, numSigs)
|
||||
for i := 0; i < numSigs; i++ {
|
||||
sigs = append(sigs, ReadSignature(r))
|
||||
@@ -89,44 +248,66 @@ func ReadValidation(r io.Reader) Validation {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Validation) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(UInt64(len(self.Signatures)), w, n, err)
|
||||
n, err = WriteTo(UInt64(len(self.Adjustments)), w, n, err)
|
||||
for _, sig := range self.Signatures {
|
||||
func (v *Validation) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(UInt32(len(v.Signatures)), w, n, err)
|
||||
n, err = WriteTo(UInt32(len(v.Adjustments)), w, n, err)
|
||||
for _, sig := range v.Signatures {
|
||||
n, err = WriteTo(sig, w, n, err)
|
||||
}
|
||||
for _, adj := range self.Adjustments {
|
||||
for _, adj := range v.Adjustments {
|
||||
n, err = WriteTo(adj, w, n, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/* Block > Txs */
|
||||
func (v *Validation) Hash() []byte {
|
||||
if v.hash != nil {
|
||||
return v.hash
|
||||
} else {
|
||||
hasher := sha256.New()
|
||||
_, err := v.WriteTo(hasher)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.hash = hasher.Sum(nil)
|
||||
return v.hash
|
||||
}
|
||||
}
|
||||
|
||||
/* Txs is part of a block */
|
||||
type Txs struct {
|
||||
Txs []Tx
|
||||
|
||||
// Volatile
|
||||
hash []byte
|
||||
}
|
||||
|
||||
func ReadTxs(r io.Reader) Txs {
|
||||
numTxs := int(ReadUInt64(r))
|
||||
numTxs := int(ReadUInt32(r))
|
||||
txs := make([]Tx, 0, numTxs)
|
||||
for i := 0; i < numTxs; i++ {
|
||||
txs = append(txs, ReadTx(r))
|
||||
}
|
||||
return Txs{txs}
|
||||
return Txs{Txs: txs}
|
||||
}
|
||||
|
||||
func (self *Txs) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(UInt64(len(self.Txs)), w, n, err)
|
||||
for _, tx := range self.Txs {
|
||||
func (txs *Txs) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(UInt32(len(txs.Txs)), w, n, err)
|
||||
for _, tx := range txs.Txs {
|
||||
n, err = WriteTo(tx, w, n, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Txs) MerkleHash() ByteSlice {
|
||||
bs := make([]Binary, 0, len(self.Txs))
|
||||
for i, tx := range self.Txs {
|
||||
bs[i] = Binary(tx)
|
||||
func (txs *Txs) Hash() []byte {
|
||||
if txs.hash != nil {
|
||||
return txs.hash
|
||||
} else {
|
||||
bs := make([]Binary, 0, len(txs.Txs))
|
||||
for i, tx := range txs.Txs {
|
||||
bs[i] = Binary(tx)
|
||||
}
|
||||
txs.hash = merkle.HashFromBinarySlice(bs)
|
||||
return txs.hash
|
||||
}
|
||||
return merkle.HashFromBinarySlice(bs)
|
||||
}
|
||||
|
||||
@@ -1,660 +0,0 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
db_ "github.com/tendermint/tendermint/db"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
)
|
||||
|
||||
var dbKeyState = []byte("state")
|
||||
|
||||
const (
|
||||
blocksInfoCh = byte(0x10) // For requests & cancellations
|
||||
blocksDataCh = byte(0x11) // For data
|
||||
|
||||
msgTypeUnknown = Byte(0x00)
|
||||
msgTypeState = Byte(0x01)
|
||||
msgTypeRequest = Byte(0x02)
|
||||
msgTypeData = Byte(0x03)
|
||||
|
||||
maxRequestsPerPeer = 2 // Maximum number of outstanding requests from peer.
|
||||
maxRequestsPerData = 2 // Maximum number of outstanding requests of some data.
|
||||
maxRequestAheadBlock = 5 // Maximum number of blocks to request ahead of current verified. Must be >= 1
|
||||
|
||||
defaultRequestTimeoutS =
|
||||
timeoutRepeatTimerMS = 1000 // Handle timed out requests periodically
|
||||
)
|
||||
|
||||
/*
|
||||
TODO: keep a heap of dataRequests * their corresponding timeouts.
|
||||
timeout dataRequests and update the peerState,
|
||||
TODO: need to keep track of progress, blocks are too large. or we need to chop into chunks.
|
||||
TODO: need to validate blocks. :/
|
||||
TODO: actually save the block.
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
dataTypeBlock = byte(0x00)
|
||||
// TODO: allow for more types, such as specific transactions
|
||||
)
|
||||
|
||||
type dataKey struct {
|
||||
dataType byte
|
||||
height uint64
|
||||
}
|
||||
|
||||
func newDataKey(dataType byte, height uint64) dataKey {
|
||||
return dataKey{dataType, height}
|
||||
}
|
||||
|
||||
func readDataKey(r io.Reader) dataKey {
|
||||
return dataKey{
|
||||
dataType: ReadByte(r),
|
||||
height: ReadUInt64(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (dk dataKey) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(dk.dataType, w, n, err)
|
||||
n, err = WriteTo(dk.height, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (dk dataKey) String() string {
|
||||
switch dataType {
|
||||
case dataTypeBlock:
|
||||
return dataKeyfmt.Sprintf("B%v", height)
|
||||
default:
|
||||
Panicf("Unknown datatype %X", dataType)
|
||||
return "" // should not happen
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type BlockManager struct {
|
||||
db *db_.LevelDB
|
||||
sw *p2p.Switch
|
||||
swEvents chan interface{}
|
||||
state *blockManagerState
|
||||
timeoutTimer *RepeatTimer
|
||||
quit chan struct{}
|
||||
started uint32
|
||||
stopped uint32
|
||||
}
|
||||
|
||||
func NewBlockManager(sw *p2p.Switch, db *db_.LevelDB) *BlockManager {
|
||||
swEvents := make(chan interface{})
|
||||
sw.AddEventListener("BlockManager.swEvents", swEvents)
|
||||
bm := &BlockManager{
|
||||
db: db,
|
||||
sw: sw,
|
||||
swEvents: swEvents,
|
||||
state: newBlockManagerState(),
|
||||
timeoutTimer: NewRepeatTimer(timeoutRepeatTimerMS * time.Second),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
bm.loadState()
|
||||
return bm
|
||||
}
|
||||
|
||||
func (bm *BlockManager) Start() {
|
||||
if atomic.CompareAndSwapUint32(&bm.started, 0, 1) {
|
||||
log.Info("Starting BlockManager")
|
||||
go bm.switchEventsHandler()
|
||||
go bm.blocksInfoHandler()
|
||||
go bm.blocksDataHandler()
|
||||
go bm.requestTimeoutHandler()
|
||||
}
|
||||
}
|
||||
|
||||
func (bm *BlockManager) Stop() {
|
||||
if atomic.CompareAndSwapUint32(&bm.stopped, 0, 1) {
|
||||
log.Info("Stopping BlockManager")
|
||||
close(bm.quit)
|
||||
close(bm.swEvents)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: assumes that data is already validated.
|
||||
// "request" is optional, it's the request response that supplied
|
||||
// the data.
|
||||
func (bm *BlockManager) StoreBlock(block *Block, origin *dataRequest) {
|
||||
dataKey := newDataKey(dataTypeBlock, uint64(block.Header.Height))
|
||||
|
||||
// XXX actually save the block.
|
||||
|
||||
canceled, newHeight := bm.state.didGetDataFromPeer(dataKey, origin.peer)
|
||||
|
||||
// Notify peers that the request has been canceled.
|
||||
for _, request := range canceled {
|
||||
msg := &requestMessage{
|
||||
key: dataKey,
|
||||
type_: requestTypeCanceled,
|
||||
}
|
||||
tm := p2p.TypedMessage{msgTypeRequest, msg}
|
||||
request.peer.TrySend(blocksInfoCh, tm.Bytes())
|
||||
}
|
||||
|
||||
// If we have new data that extends our contiguous range, then announce it.
|
||||
if newHeight {
|
||||
bm.sw.Broadcast(blocksInfoCh, bm.state.makeStateMessage())
|
||||
}
|
||||
}
|
||||
|
||||
func (bm *BlockManager) LoadBlock(height uint64) *Block {
|
||||
panic("not yet implemented")
|
||||
}
|
||||
|
||||
// Handle peer new/done events
|
||||
func (bm *BlockManager) switchEventsHandler() {
|
||||
for {
|
||||
swEvent, ok := <-bm.swEvents
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch swEvent.(type) {
|
||||
case p2p.SwitchEventNewPeer:
|
||||
event := swEvent.(p2p.SwitchEventNewPeer)
|
||||
// Create peerState for event.Peer
|
||||
bm.state.createEntryForPeer(event.Peer)
|
||||
// Share our state with event.Peer
|
||||
msg := &stateMessage{
|
||||
lastBlockHeight: UInt64(bm.state.lastBlockHeight),
|
||||
}
|
||||
tm := p2p.TypedMessage{msgTypeRequest, msg}
|
||||
event.Peer.TrySend(blocksInfoCh, tm.Bytes())
|
||||
case p2p.SwitchEventDonePeer:
|
||||
event := swEvent.(p2p.SwitchEventDonePeer)
|
||||
// Delete peerState for event.Peer
|
||||
bm.state.deleteEntryForPeer(event.Peer)
|
||||
default:
|
||||
log.Warning("Unhandled switch event type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle requests/cancellations from the blocksInfo channel
|
||||
func (bm *BlockManager) blocksInfoHandler() {
|
||||
for {
|
||||
inMsg, ok := bm.sw.Receive(blocksInfoCh)
|
||||
if !ok {
|
||||
break // Client has stopped
|
||||
}
|
||||
|
||||
msg := decodeMessage(inMsg.Bytes)
|
||||
log.Info("blocksInfoHandler received %v", msg)
|
||||
|
||||
switch msg.(type) {
|
||||
case *stateMessage:
|
||||
m := msg.(*stateMessage)
|
||||
peerState := bm.getPeerState(inMsg.MConn.Peer)
|
||||
if peerState == nil {
|
||||
continue // peer has since been disconnected.
|
||||
}
|
||||
newDataTypes := peerState.applyStateMessage(m)
|
||||
// Consider requesting data.
|
||||
// Does the peer claim to have something we want?
|
||||
FOR_LOOP:
|
||||
for _, newDataType := range newDataTypes {
|
||||
// Are we already requesting too much data from peer?
|
||||
if !peerState.canRequestMore() {
|
||||
break FOR_LOOP
|
||||
}
|
||||
for _, wantedKey := range bm.state.nextWantedKeysForType(newDataType) {
|
||||
if !peerState.hasData(wantedKey) {
|
||||
break FOR_LOOP
|
||||
}
|
||||
// Request wantedKey from peer.
|
||||
msg := &requestMessage{
|
||||
key: dataKey,
|
||||
type_: requestTypeFetch,
|
||||
}
|
||||
tm := p2p.TypedMessage{msgTypeRequest, msg}
|
||||
sent := inMsg.MConn.Peer.TrySend(blocksInfoCh, tm.Bytes())
|
||||
if sent {
|
||||
// Log the request
|
||||
request := &dataRequest{
|
||||
peer: inMsg.MConn.Peer,
|
||||
key: wantedKey,
|
||||
time: time.Now(),
|
||||
timeout: time.Now().Add(defaultRequestTimeout
|
||||
}
|
||||
bm.state.addDataRequest(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
case *requestMessage:
|
||||
m := msg.(*requestMessage)
|
||||
switch m.type_ {
|
||||
case requestTypeFetch:
|
||||
// TODO: prevent abuse.
|
||||
if !inMsg.MConn.Peer.CanSend(blocksDataCh) {
|
||||
msg := &requestMessage{
|
||||
key: dataKey,
|
||||
type_: requestTypeTryAgain,
|
||||
}
|
||||
tm := p2p.TypedMessage{msgTypeRequest, msg}
|
||||
sent := inMsg.MConn.Peer.TrySend(blocksInfoCh, tm.Bytes())
|
||||
} else {
|
||||
// If we don't have it, log and ignore.
|
||||
block := bm.LoadBlock(m.key.height)
|
||||
if block == nil {
|
||||
log.Warning("Peer %v asked for nonexistant block %v", inMsg.MConn.Peer, m.key)
|
||||
}
|
||||
// Send the data.
|
||||
msg := &dataMessage{
|
||||
key: dataKey,
|
||||
bytes: BinaryBytes(block),
|
||||
}
|
||||
tm := p2p.TypedMessage{msgTypeData, msg}
|
||||
inMsg.MConn.Peer.TrySend(blocksDataCh, tm.Bytes())
|
||||
}
|
||||
case requestTypeCanceled:
|
||||
// TODO: handle
|
||||
// This requires modifying mconnection to keep track of item keys.
|
||||
case requestTypeTryAgain:
|
||||
// TODO: handle
|
||||
default:
|
||||
log.Warning("Invalid request: %v", m)
|
||||
// Ignore.
|
||||
}
|
||||
default:
|
||||
// should not happen
|
||||
Panicf("Unknown message %v", msg)
|
||||
// bm.sw.StopPeerForError(inMsg.MConn.Peer, errInvalidMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
}
|
||||
|
||||
// Handle receiving data from the blocksData channel
|
||||
func (bm *BlockManager) blocksDataHandler() {
|
||||
for {
|
||||
inMsg, ok := bm.sw.Receive(blocksDataCh)
|
||||
if !ok {
|
||||
break // Client has stopped
|
||||
}
|
||||
|
||||
msg := decodeMessage(inMsg.Bytes)
|
||||
log.Info("blocksDataHandler received %v", msg)
|
||||
|
||||
switch msg.(type) {
|
||||
case *dataMessage:
|
||||
// See if we want the data.
|
||||
// Validate data.
|
||||
// Add to db.
|
||||
// Update state & broadcast as necessary.
|
||||
default:
|
||||
// Ignore unknown message
|
||||
// bm.sw.StopPeerForError(inMsg.MConn.Peer, errInvalidMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
}
|
||||
|
||||
// Handle timed out requests by requesting from others.
|
||||
func (bm *BlockManager) requestTimeoutHandler() {
|
||||
for {
|
||||
_, ok := <-bm.timeoutTimer
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
// Iterate over requests by time and handle timed out requests.
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// blockManagerState keeps track of which block parts are stored locally.
|
||||
// It's also persisted via JSON in the db.
|
||||
type blockManagerState struct {
|
||||
mtx sync.Mutex
|
||||
lastBlockHeight uint64 // Last contiguous header height
|
||||
otherBlockHeights map[uint64]struct{}
|
||||
requestsByKey map[dataKey][]*dataRequest
|
||||
requestsByTimeout *Heap // Could be a linkedlist, but more flexible.
|
||||
peerStates map[string]*peerState
|
||||
}
|
||||
|
||||
func newBlockManagerState() *blockManagerState {
|
||||
return &blockManagerState{
|
||||
requestsByKey: make(map[dataKey][]*dataRequest),
|
||||
requestsByTimeout: NewHeap(),
|
||||
peerStates: make(map[string]*peerState),
|
||||
}
|
||||
}
|
||||
|
||||
type blockManagerStateJSON struct {
|
||||
LastBlockHeight uint64 // Last contiguous header height
|
||||
OtherBlockHeights map[uint64]struct{}
|
||||
}
|
||||
|
||||
func (bms *BlockManagerState) loadState(db _db.LevelDB) {
|
||||
bms.mtx.Lock()
|
||||
defer bms.mtx.Unlock()
|
||||
stateBytes := db.Get(dbKeyState)
|
||||
if stateBytes == nil {
|
||||
log.Info("New BlockManager with no state")
|
||||
} else {
|
||||
bmsJSON := &blockManagerStateJSON{}
|
||||
err := json.Unmarshal(stateBytes, bmsJSON)
|
||||
if err != nil {
|
||||
Panicf("Could not unmarshal state bytes: %X", stateBytes)
|
||||
}
|
||||
bms.lastBlockHeight = bmsJSON.LastBlockHeight
|
||||
bms.otherBlockHeights = bmsJSON.OtherBlockHeights
|
||||
}
|
||||
}
|
||||
|
||||
func (bms *BlockManagerState) saveState(db _db.LevelDB) {
|
||||
bms.mtx.Lock()
|
||||
defer bms.mtx.Unlock()
|
||||
bmsJSON := &blockManagerStateJSON{
|
||||
LastBlockHeight: bms.lastBlockHeight,
|
||||
OtherBlockHeights: bms.otherBlockHeights,
|
||||
}
|
||||
stateBytes, err := json.Marshal(bmsJSON)
|
||||
if err != nil {
|
||||
panic("Could not marshal state bytes")
|
||||
}
|
||||
db.Set(dbKeyState, stateBytes)
|
||||
}
|
||||
|
||||
func (bms *blockManagerState) makeStateMessage() *stateMessage {
|
||||
bms.mtx.Lock()
|
||||
defer bms.mtx.Unlock()
|
||||
return &stateMessage{
|
||||
lastBlockHeight: UInt64(bms.lastBlockHeight),
|
||||
}
|
||||
}
|
||||
|
||||
func (bms *blockManagerState) createEntryForPeer(peer *peer) {
|
||||
bms.mtx.Lock()
|
||||
defer bms.mtx.Unlock()
|
||||
bms.peerStates[peer.Key] = &peerState{peer: peer}
|
||||
}
|
||||
|
||||
func (bms *blockManagerState) deleteEntryForPeer(peer *peer) {
|
||||
bms.mtx.Lock()
|
||||
defer bms.mtx.Unlock()
|
||||
delete(bms.peerStates, peer.Key)
|
||||
}
|
||||
|
||||
func (bms *blockManagerState) getPeerState(peer *Peer) {
|
||||
bms.mtx.Lock()
|
||||
defer bms.mtx.Unlock()
|
||||
return bms.peerStates[peer.Key]
|
||||
}
|
||||
|
||||
func (bms *blockManagerState) addDataRequest(newRequest *dataRequest) {
|
||||
ps.mtx.Lock()
|
||||
bms.requestsByKey[newRequest.key] = append(bms.requestsByKey[newRequest.key], newRequest)
|
||||
bms.requestsByTimeout.Push(newRequest) // XXX
|
||||
peerState, ok := bms.peerStates[newRequest.peer.Key]
|
||||
ps.mtx.Unlock()
|
||||
if ok {
|
||||
peerState.addDataRequest(newRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func (bms *blockManagerState) didGetDataFromPeer(key dataKey, peer *p2p.Peer) (canceled []*dataRequest, newHeight bool) {
|
||||
bms.mtx.Lock()
|
||||
defer bms.mtx.Unlock()
|
||||
if key.dataType != dataTypeBlock {
|
||||
Panicf("Unknown datatype %X", key.dataType)
|
||||
}
|
||||
// Adjust lastBlockHeight/otherBlockHeights.
|
||||
height := key.height
|
||||
if bms.lastBlockHeight == height-1 {
|
||||
bms.lastBlockHeight = height
|
||||
height++
|
||||
for _, ok := bms.otherBlockHeights[height]; ok; {
|
||||
delete(bms.otherBlockHeights, height)
|
||||
bms.lastBlockHeight = height
|
||||
height++
|
||||
}
|
||||
newHeight = true
|
||||
}
|
||||
// Remove dataRequests
|
||||
requests := bms.requestsByKey[key]
|
||||
for _, request := range requests {
|
||||
peerState, ok := bms.peerStates[peer.Key]
|
||||
if ok {
|
||||
peerState.removeDataRequest(request)
|
||||
}
|
||||
if request.peer == peer {
|
||||
continue
|
||||
}
|
||||
canceled = append(canceled, request)
|
||||
}
|
||||
delete(bms.requestsByKey, key)
|
||||
|
||||
return canceled, newHeight
|
||||
}
|
||||
|
||||
// Returns at most maxRequestAheadBlock dataKeys that we don't yet have &
|
||||
// aren't already requesting from maxRequestsPerData peers.
|
||||
func (bms *blockManagerState) nextWantedKeysForType(dataType byte) []dataKey {
|
||||
bms.mtx.Lock()
|
||||
defer bms.mtx.Unlock()
|
||||
var keys []dataKey
|
||||
switch dataType {
|
||||
case dataTypeBlock:
|
||||
for h := bms.lastBlockHeight + 1; h <= bms.lastBlockHeight+maxRequestAheadBlock; h++ {
|
||||
if _, ok := bms.otherBlockHeights[h]; !ok {
|
||||
key := newDataKey(dataTypeBlock, h)
|
||||
if len(bms.requestsByKey[key]) < maxRequestsPerData {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys
|
||||
default:
|
||||
Panicf("Unknown datatype %X", dataType)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// dataRequest keeps track of each request for a given peice of data & peer.
|
||||
type dataRequest struct {
|
||||
peer *p2p.Peer
|
||||
key dataKey
|
||||
time time.Time
|
||||
timeout time.Time
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type peerState struct {
|
||||
mtx sync.Mutex
|
||||
peer *Peer
|
||||
lastBlockHeight uint64 // Last contiguous header height
|
||||
requests []*dataRequest // Active requests
|
||||
// XXX we need to
|
||||
}
|
||||
|
||||
// Returns which dataTypes are new as declared by stateMessage.
|
||||
func (ps *peerState) applyStateMessage(msg *stateMessage) []byte {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
var newTypes []byte
|
||||
if uint64(msg.lastBlockHeight) > ps.lastBlockHeight {
|
||||
newTypes = append(newTypes, dataTypeBlock)
|
||||
ps.lastBlockHeight = uint64(msg.lastBlockHeight)
|
||||
} else {
|
||||
log.Info("Strange, peer declares a regression of %X", dataTypeBlock)
|
||||
}
|
||||
return newTypes
|
||||
}
|
||||
|
||||
func (ps *peerState) hasData(key dataKey) bool {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
switch key.dataType {
|
||||
case dataTypeBlock:
|
||||
return key.height <= ps.lastBlockHeight
|
||||
default:
|
||||
Panicf("Unknown datatype %X", dataType)
|
||||
return false // should not happen
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *peerState) addDataRequest(newRequest *dataRequest) {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
for _, request := range ps.requests {
|
||||
if request.key == newRequest.key {
|
||||
return
|
||||
}
|
||||
}
|
||||
ps.requests = append(ps.requests, newRequest)
|
||||
return newRequest
|
||||
}
|
||||
|
||||
func (ps *peerState) remoteDataRequest(key dataKey) bool {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
filtered := []*dataRequest{}
|
||||
removed := false
|
||||
for _, request := range ps.requests {
|
||||
if request.key == key {
|
||||
removed = true
|
||||
} else {
|
||||
filtered = append(filtered, request)
|
||||
}
|
||||
}
|
||||
ps.requests = filtered
|
||||
return removed
|
||||
}
|
||||
|
||||
func (ps *peerState) canRequestMore() bool {
|
||||
ps.mtx.Lock()
|
||||
defer ps.mtx.Unlock()
|
||||
return len(ps.requests) < maxRequestsPerPeer
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/* Messages */
|
||||
|
||||
// TODO: check for unnecessary extra bytes at the end.
|
||||
func decodeMessage(bz ByteSlice) (msg interface{}) {
|
||||
// log.Debug("decoding msg bytes: %X", bz)
|
||||
switch Byte(bz[0]) {
|
||||
case msgTypeState:
|
||||
return &stateMessage{}
|
||||
case msgTypeRequest:
|
||||
return readRequestMessage(bytes.NewReader(bz[1:]))
|
||||
case msgTypeData:
|
||||
return readDataMessage(bytes.NewReader(bz[1:]))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
A stateMessage declares what (contiguous) blocks & headers are known.
|
||||
*/
|
||||
type stateMessage struct {
|
||||
lastBlockHeight UInt64 // Last contiguous block height
|
||||
}
|
||||
|
||||
func readStateMessage(r io.Reader) *stateMessage {
|
||||
return &stateMessage{
|
||||
lastBlockHeight: ReadUInt64(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *stateMessage) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(msgTypeState, w, n, err)
|
||||
n, err = WriteTo(m.lastBlockHeight, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *stateMessage) String() string {
|
||||
return fmt.Sprintf("[State B:%v]", m.lastBlockHeight)
|
||||
}
|
||||
|
||||
/*
|
||||
A requestMessage requests a block and/or header at a given height.
|
||||
*/
|
||||
type requestMessage struct {
|
||||
key dataKey
|
||||
type_ Byte
|
||||
}
|
||||
|
||||
const (
|
||||
requestTypeFetch = Byte(0x01)
|
||||
requestTypeCanceled = Byte(0x02)
|
||||
requestTypeTryAgain = Byte(0x03)
|
||||
)
|
||||
|
||||
func readRequestMessage(r io.Reader) *requestMessage {
|
||||
return &requestMessage{
|
||||
key: ReadDataKey(r),
|
||||
type_: ReadByte(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *requestMessage) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(msgTypeRequest, w, n, err)
|
||||
n, err = WriteTo(m.key, w, n, err)
|
||||
n, err = WriteTo(m.type_, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *requestMessage) String() string {
|
||||
switch m.type_ {
|
||||
case requestTypeByte:
|
||||
return fmt.Sprintf("[Request(fetch) %v]", m.key)
|
||||
case requestTypeCanceled:
|
||||
return fmt.Sprintf("[Request(canceled) %v]", m.key)
|
||||
case requestTypeTryAgain:
|
||||
return fmt.Sprintf("[Request(tryagain) %v]", m.key)
|
||||
default:
|
||||
return fmt.Sprintf("[Request(invalid) %v]", m.key)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
A dataMessage contains block data, maybe requested.
|
||||
The data can be a Validation, Txs, or whole Block object.
|
||||
*/
|
||||
type dataMessage struct {
|
||||
key dataKey
|
||||
bytes ByteSlice
|
||||
}
|
||||
|
||||
func readDataMessage(r io.Reader) *dataMessage {
|
||||
return &dataMessage{
|
||||
key: readDataKey(r),
|
||||
bytes: readByteSlice(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *dataMessage) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(msgTypeData, w, n, err)
|
||||
n, err = WriteTo(m.key, w, n, err)
|
||||
n, err = WriteTo(m.bytes, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *dataMessage) String() string {
|
||||
return fmt.Sprintf("[Data %v]", m.key)
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Helper for keeping track of block parts.
|
||||
type BlockPartSet struct {
|
||||
mtx sync.Mutex
|
||||
signer *Account
|
||||
height uint32
|
||||
round uint16 // Not used
|
||||
total uint16
|
||||
numParts uint16
|
||||
parts []*BlockPart
|
||||
|
||||
_block *Block // cache
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidBlockPartSignature = errors.New("Invalid block part signature") // Peer gave us a fake part
|
||||
ErrInvalidBlockPartConflict = errors.New("Invalid block part conflict") // Signer signed conflicting parts
|
||||
)
|
||||
|
||||
// Signer may be nil if signer is unknown beforehand.
|
||||
func NewBlockPartSet(height uint32, round uint16, signer *Account) *BlockPartSet {
|
||||
return &BlockPartSet{
|
||||
signer: signer,
|
||||
height: height,
|
||||
round: round,
|
||||
}
|
||||
}
|
||||
|
||||
// In the case where the signer wasn't known prior to NewBlockPartSet(),
|
||||
// user should call SetSigner() prior to AddBlockPart().
|
||||
func (bps *BlockPartSet) SetSigner(signer *Account) {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
if bps.signer != nil {
|
||||
panic("BlockPartSet signer already set.")
|
||||
}
|
||||
bps.signer = signer
|
||||
}
|
||||
|
||||
func (bps *BlockPartSet) BlockParts() []*BlockPart {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
return bps.parts
|
||||
}
|
||||
|
||||
func (bps *BlockPartSet) BitArray() []byte {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
if bps.parts == nil {
|
||||
return nil
|
||||
}
|
||||
bitArray := make([]byte, (len(bps.parts)+7)/8)
|
||||
for i, part := range bps.parts {
|
||||
if part != nil {
|
||||
bitArray[i/8] |= 1 << uint(i%8)
|
||||
}
|
||||
}
|
||||
return bitArray
|
||||
}
|
||||
|
||||
// If the part isn't valid, returns an error.
|
||||
// err can be ErrInvalidBlockPart[Conflict|Signature]
|
||||
func (bps *BlockPartSet) AddBlockPart(part *BlockPart) (added bool, err error) {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
|
||||
// If part is invalid, return an error.
|
||||
err = part.ValidateWithSigner(bps.signer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if bps.parts == nil {
|
||||
// First received part for this round.
|
||||
bps.parts = make([]*BlockPart, part.Total)
|
||||
bps.total = uint16(part.Total)
|
||||
bps.parts[int(part.Index)] = part
|
||||
bps.numParts++
|
||||
return true, nil
|
||||
} else {
|
||||
// Check part.Index and part.Total
|
||||
if uint16(part.Index) >= bps.total {
|
||||
return false, ErrInvalidBlockPartConflict
|
||||
}
|
||||
if uint16(part.Total) != bps.total {
|
||||
return false, ErrInvalidBlockPartConflict
|
||||
}
|
||||
// Check for existing parts.
|
||||
existing := bps.parts[part.Index]
|
||||
if existing != nil {
|
||||
if existing.Bytes.Equals(part.Bytes) {
|
||||
// Ignore duplicate
|
||||
return false, nil
|
||||
} else {
|
||||
return false, ErrInvalidBlockPartConflict
|
||||
}
|
||||
} else {
|
||||
bps.parts[int(part.Index)] = part
|
||||
bps.numParts++
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (bps *BlockPartSet) IsComplete() bool {
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
return bps.total > 0 && bps.total == bps.numParts
|
||||
}
|
||||
|
||||
func (bps *BlockPartSet) Block() *Block {
|
||||
if !bps.IsComplete() {
|
||||
return nil
|
||||
}
|
||||
bps.mtx.Lock()
|
||||
defer bps.mtx.Unlock()
|
||||
if bps._block == nil {
|
||||
blockBytes := []byte{}
|
||||
for _, part := range bps.parts {
|
||||
blockBytes = append(blockBytes, part.Bytes...)
|
||||
}
|
||||
block := ReadBlock(bytes.NewReader(blockBytes))
|
||||
bps._block = block
|
||||
}
|
||||
return bps._block
|
||||
}
|
||||
+1
-1
@@ -10,6 +10,6 @@ func init() {
|
||||
logging.SetFormatter(logging.MustStringFormatter("[%{level:.1s}] %{message}"))
|
||||
}
|
||||
|
||||
func SetLogger(l *logging.Logger) {
|
||||
func SetBlocksLogger(l *logging.Logger) {
|
||||
log = l
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
Signature message wire format:
|
||||
|
||||
|A...|SSS...|
|
||||
|
||||
A account number, varint encoded (1+ bytes)
|
||||
S signature of all prior bytes (32 bytes)
|
||||
|
||||
It usually follows the message to be signed.
|
||||
|
||||
*/
|
||||
|
||||
type Signature struct {
|
||||
Signer AccountId
|
||||
SigBytes ByteSlice
|
||||
}
|
||||
|
||||
func ReadSignature(r io.Reader) Signature {
|
||||
return Signature{
|
||||
Signer: ReadAccountId(r),
|
||||
SigBytes: ReadByteSlice(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (self Signature) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(self.Signer, w, n, err)
|
||||
n, err = WriteTo(self.SigBytes, w, n, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Signature) Verify(msg ByteSlice) bool {
|
||||
return false
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
)
|
||||
|
||||
var (
|
||||
blockStoreKey = []byte("blockStore")
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type BlockStoreJSON struct {
|
||||
Height uint32
|
||||
}
|
||||
|
||||
func (bsj BlockStoreJSON) Save(db *leveldb.DB) {
|
||||
bytes, err := json.Marshal(bsj)
|
||||
if err != nil {
|
||||
Panicf("Could not marshal state bytes: %v", err)
|
||||
}
|
||||
db.Put(blockStoreKey, bytes, nil)
|
||||
}
|
||||
|
||||
func LoadBlockStoreJSON(db *leveldb.DB) BlockStoreJSON {
|
||||
bytes, err := db.Get(blockStoreKey, nil)
|
||||
if err != nil {
|
||||
Panicf("Could not load BlockStoreJSON from db: %v", err)
|
||||
}
|
||||
if bytes == nil {
|
||||
return BlockStoreJSON{
|
||||
Height: 0,
|
||||
}
|
||||
}
|
||||
bsj := BlockStoreJSON{}
|
||||
err = json.Unmarshal(bytes, &bsj)
|
||||
if err != nil {
|
||||
Panicf("Could not unmarshal bytes: %X", bytes)
|
||||
}
|
||||
return bsj
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
Simple low level store for blocks, which is actually stored as separte parts (wire format).
|
||||
*/
|
||||
type BlockStore struct {
|
||||
height uint32
|
||||
db *leveldb.DB
|
||||
}
|
||||
|
||||
func NewBlockStore(db *leveldb.DB) *BlockStore {
|
||||
bsjson := LoadBlockStoreJSON(db)
|
||||
return &BlockStore{
|
||||
height: bsjson.Height,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Height() returns the last known contiguous block height.
|
||||
func (bs *BlockStore) Height() uint32 {
|
||||
return bs.height
|
||||
}
|
||||
|
||||
// LoadBlockPart loads a part of a block.
|
||||
func (bs *BlockStore) LoadBlockPart(height uint32, index uint16) *BlockPart {
|
||||
partBytes, err := bs.db.Get(calcBlockPartKey(height, index), nil)
|
||||
if err != nil {
|
||||
Panicf("Could not load block part: %v", err)
|
||||
}
|
||||
if partBytes == nil {
|
||||
return nil
|
||||
}
|
||||
return ReadBlockPart(bytes.NewReader(partBytes))
|
||||
}
|
||||
|
||||
// Convenience method for loading block parts and merging to a block.
|
||||
func (bs *BlockStore) LoadBlock(height uint32) *Block {
|
||||
// Get the first part.
|
||||
part0 := bs.LoadBlockPart(height, 0)
|
||||
if part0 == nil {
|
||||
return nil
|
||||
}
|
||||
// XXX implement
|
||||
panic("TODO: Not implemented")
|
||||
}
|
||||
|
||||
func (bs *BlockStore) StageBlockAndParts(block *Block, parts []*BlockPart) error {
|
||||
// XXX validate
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: Assumes that parts as well as the block are valid. See StageBlockParts().
|
||||
// Writes are synchronous and atomic.
|
||||
func (bs *BlockStore) SaveBlockParts(height uint32, parts []*BlockPart) error {
|
||||
if height != bs.height+1 {
|
||||
return Errorf("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.height+1, height)
|
||||
}
|
||||
// Save parts
|
||||
batch := new(leveldb.Batch)
|
||||
for _, part := range parts {
|
||||
partBytes := BinaryBytes(part)
|
||||
batch.Put(calcBlockPartKey(uint32(part.Height), uint16(part.Index)), partBytes)
|
||||
}
|
||||
err := bs.db.Write(batch, &opt.WriteOptions{Sync: true})
|
||||
// Save new BlockStoreJSON descriptor
|
||||
BlockStoreJSON{Height: height}.Save(bs.db)
|
||||
return err
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
func calcBlockPartKey(height uint32, index uint16) []byte {
|
||||
buf := [11]byte{'B'}
|
||||
binary.BigEndian.PutUint32(buf[1:9], height)
|
||||
binary.BigEndian.PutUint16(buf[9:11], index)
|
||||
return buf[:]
|
||||
}
|
||||
+2
-2
@@ -35,7 +35,7 @@ func ReadTx(r io.Reader) Tx {
|
||||
case TX_TYPE_SEND:
|
||||
return &SendTx{
|
||||
Fee: ReadUInt64(r),
|
||||
To: ReadAccountId(r),
|
||||
To: ReadUInt64(r),
|
||||
Amount: ReadUInt64(r),
|
||||
Signature: ReadSignature(r),
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func ReadTx(r io.Reader) Tx {
|
||||
|
||||
type SendTx struct {
|
||||
Fee UInt64
|
||||
To AccountId
|
||||
To UInt64
|
||||
Amount UInt64
|
||||
Signature
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
. "github.com/tendermint/tendermint/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
/*
|
||||
The full vote structure is only needed when presented as evidence.
|
||||
Typically only the signature is passed around, as the hash & height are implied.
|
||||
*/
|
||||
|
||||
type Vote struct {
|
||||
Height UInt64
|
||||
BlockHash ByteSlice
|
||||
Signature
|
||||
}
|
||||
|
||||
func ReadVote(r io.Reader) Vote {
|
||||
return Vote{
|
||||
Height: ReadUInt64(r),
|
||||
BlockHash: ReadByteSlice(r),
|
||||
Signature: ReadSignature(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (self Vote) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = WriteTo(self.Height, w, n, err)
|
||||
n, err = WriteTo(self.BlockHash, w, n, err)
|
||||
n, err = WriteTo(self.Signature, w, n, err)
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user