Merge branch 'statecache' into rpc

This commit is contained in:
Ethan Buchman
2015-03-29 18:03:03 -07:00
45 changed files with 2668 additions and 2037 deletions

View File

@ -38,13 +38,13 @@ type Account struct {
StorageRoot []byte // VM storage merkle root.
}
func (account *Account) Copy() *Account {
accountCopy := *account
return &accountCopy
func (acc *Account) Copy() *Account {
accCopy := *acc
return &accCopy
}
func (account *Account) String() string {
return fmt.Sprintf("Account{%X:%v C:%v S:%X}", account.Address, account.PubKey, len(account.Code), account.StorageRoot)
func (acc *Account) String() string {
return fmt.Sprintf("Account{%X:%v C:%v S:%X}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot)
}
func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) {

View File

@ -2,6 +2,7 @@ package binary
import (
"bytes"
"fmt"
"reflect"
"testing"
"time"
@ -58,6 +59,35 @@ var _ = RegisterInterface(
ConcreteType{&Viper{}},
)
func TestAnimalInterface(t *testing.T) {
var foo Animal
// Type of pointer to Animal
rt := reflect.TypeOf(&foo)
fmt.Printf("rt: %v\n", rt)
// Type of Animal itself.
// NOTE: normally this is acquired through other means
// like introspecting on method signatures, or struct fields.
rte := rt.Elem()
fmt.Printf("rte: %v\n", rte)
// Get a new pointer to the interface
// NOTE: calling .Interface() is to get the actual value,
// instead of reflection values.
ptr := reflect.New(rte).Interface()
fmt.Printf("ptr: %v", ptr)
// Make a binary byteslice that represents a snake.
snakeBytes := BinaryBytes(Snake([]byte("snake")))
snakeReader := bytes.NewReader(snakeBytes)
// Now you can read it.
n, err := new(int64), new(error)
it := *ReadBinary(ptr, snakeReader, n, err).(*Animal)
fmt.Println(it, reflect.TypeOf(it))
}
//-------------------------------------
type Constructor func() interface{}
@ -287,9 +317,9 @@ func validateComplexArray(o interface{}, t *testing.T) {
var testCases = []TestCase{}
func init() {
//testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic})
//testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex})
//testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2})
testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic})
testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex})
testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2})
testCases = append(testCases, TestCase{constructComplexArray, instantiateComplexArray, validateComplexArray})
}

View File

@ -1,7 +1,7 @@
package blockchain
import (
"math/rand"
"sync"
"sync/atomic"
"time"
@ -10,306 +10,305 @@ import (
)
const (
maxOutstandingRequestsPerPeer = 10
eventsChannelCapacity = 100
requestTimeoutSeconds = 10
maxTries = 3
inputsChannelCapacity = 200
requestIntervalMS = 500
requestBatchSize = 50
maxPendingRequests = 50
maxTotalRequests = 100
maxPeersPerRequest = 1
maxPendingRequests = 200
maxTotalRequests = 300
maxRequestsPerPeer = 300
)
type BlockRequest struct {
Height uint
PeerId string
}
var (
requestTimeoutSeconds = time.Duration(1)
)
type BlockPool struct {
peers map[string]*bpPeer
blockInfos map[uint]*bpBlockInfo
height uint // the lowest key in blockInfos.
started int32 // atomic
stopped int32 // atomic
// block requests
requestsMtx sync.Mutex
requests map[uint]*bpRequest
height uint // the lowest key in requests.
numPending int32
numTotal int32
eventsCh chan interface{} // internal events.
requestsCh chan<- BlockRequest // output of new requests to make.
timeoutsCh chan<- string // output of peers that timed out.
blocksCh chan<- *types.Block // output of ordered blocks.
repeater *RepeatTimer // for requesting more bocks.
quit chan struct{}
// peers
peersMtx sync.Mutex
peers map[string]*bpPeer
requestsCh chan<- BlockRequest
timeoutsCh chan<- string
repeater *RepeatTimer
running int32 // atomic
}
func NewBlockPool(start uint, timeoutsCh chan<- string, requestsCh chan<- BlockRequest, blocksCh chan<- *types.Block) *BlockPool {
func NewBlockPool(start uint, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool {
return &BlockPool{
peers: make(map[string]*bpPeer),
blockInfos: make(map[uint]*bpBlockInfo),
requests: make(map[uint]*bpRequest),
height: start,
started: 0,
stopped: 0,
numPending: 0,
numTotal: 0,
quit: make(chan struct{}),
eventsCh: make(chan interface{}, eventsChannelCapacity),
requestsCh: requestsCh,
timeoutsCh: timeoutsCh,
blocksCh: blocksCh,
repeater: NewRepeatTimer("", requestIntervalMS*time.Millisecond),
running: 0,
}
}
func (bp *BlockPool) Start() {
if atomic.CompareAndSwapInt32(&bp.started, 0, 1) {
func (pool *BlockPool) Start() {
if atomic.CompareAndSwapInt32(&pool.running, 0, 1) {
log.Info("Starting BlockPool")
go bp.run()
go pool.run()
}
}
func (bp *BlockPool) Stop() {
if atomic.CompareAndSwapInt32(&bp.stopped, 0, 1) {
func (pool *BlockPool) Stop() {
if atomic.CompareAndSwapInt32(&pool.running, 1, 0) {
log.Info("Stopping BlockPool")
close(bp.quit)
close(bp.eventsCh)
close(bp.requestsCh)
close(bp.timeoutsCh)
close(bp.blocksCh)
bp.repeater.Stop()
pool.repeater.Stop()
}
}
// AddBlock should be called when a block is received.
func (bp *BlockPool) AddBlock(block *types.Block, peerId string) {
bp.eventsCh <- bpBlockResponse{block, peerId}
func (pool *BlockPool) IsRunning() bool {
return atomic.LoadInt32(&pool.running) == 1
}
func (bp *BlockPool) SetPeerStatus(peerId string, height uint) {
bp.eventsCh <- bpPeerStatus{peerId, height}
}
// Runs in a goroutine and processes messages.
func (bp *BlockPool) run() {
FOR_LOOP:
// Run spawns requests as needed.
func (pool *BlockPool) run() {
RUN_LOOP:
for {
select {
case msg := <-bp.eventsCh:
bp.handleEvent(msg)
case <-bp.repeater.Ch:
bp.makeMoreBlockInfos()
bp.requestBlocksFromRandomPeers(10)
case <-bp.quit:
break FOR_LOOP
if atomic.LoadInt32(&pool.running) == 0 {
break RUN_LOOP
}
_, numPending, numTotal := pool.GetStatus()
if numPending >= maxPendingRequests {
// sleep for a bit.
time.Sleep(requestIntervalMS * time.Millisecond)
} else if numTotal >= maxTotalRequests {
// sleep for a bit.
time.Sleep(requestIntervalMS * time.Millisecond)
} else {
// request for more blocks.
height := pool.nextHeight()
pool.makeRequest(height)
}
}
}
func (bp *BlockPool) handleEvent(event_ interface{}) {
switch event := event_.(type) {
case bpBlockResponse:
peer := bp.peers[event.peerId]
blockInfo := bp.blockInfos[event.block.Height]
if blockInfo == nil {
// block was unwanted.
func (pool *BlockPool) GetStatus() (uint, int32, int32) {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
return pool.height, pool.numPending, pool.numTotal
}
// We need to see the second block's Validation to validate the first block.
// So we peek two blocks at a time.
func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
if r := pool.requests[pool.height]; r != nil {
first = r.block
}
if r := pool.requests[pool.height+1]; r != nil {
second = r.block
}
return
}
// Pop the first block at pool.height
// It must have been validated by 'second'.Validation from PeekTwoBlocks().
func (pool *BlockPool) PopRequest() {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
if r := pool.requests[pool.height]; r == nil || r.block == nil {
panic("PopRequest() requires a valid block")
}
delete(pool.requests, pool.height)
pool.height++
pool.numTotal--
}
// Invalidates the block at pool.height.
// Remove the peer and request from others.
func (pool *BlockPool) RedoRequest(height uint) {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
request := pool.requests[height]
if request.block == nil {
panic("Expected block to be non-nil")
}
pool.RemovePeer(request.peerId) // Lock on peersMtx.
request.block = nil
request.peerId = ""
pool.numPending++
go requestRoutine(pool, height)
}
func (pool *BlockPool) hasBlock(height uint) bool {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
request := pool.requests[height]
return request != nil && request.block != nil
}
func (pool *BlockPool) setPeerForRequest(height uint, peerId string) {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
request := pool.requests[height]
if request == nil {
return
}
request.peerId = peerId
}
func (pool *BlockPool) AddBlock(block *types.Block, peerId string) {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
request := pool.requests[block.Height]
if request == nil {
return
}
if request.peerId != peerId {
return
}
if request.block != nil {
return
}
request.block = block
pool.numPending--
}
func (pool *BlockPool) getPeer(peerId string) *bpPeer {
pool.peersMtx.Lock() // Lock
defer pool.peersMtx.Unlock()
peer := pool.peers[peerId]
return peer
}
// Sets the peer's blockchain height.
func (pool *BlockPool) SetPeerHeight(peerId string, height uint) {
pool.peersMtx.Lock() // Lock
defer pool.peersMtx.Unlock()
peer := pool.peers[peerId]
if peer != nil {
peer.bad++
}
peer.height = height
} else {
// block was wanted.
if peer != nil {
peer.good++
}
delete(peer.requests, event.block.Height)
if blockInfo.block == nil {
// peer is the first to give it to us.
blockInfo.block = event.block
blockInfo.blockBy = peer.id
bp.numPending--
if event.block.Height == bp.height {
go bp.pushBlocksFromStart()
}
}
}
case bpPeerStatus: // updated or new status from peer
// request blocks if possible.
peer := bp.peers[event.peerId]
if peer == nil {
peer = bpNewPeer(event.peerId, event.height)
bp.peers[peer.id] = peer
}
bp.requestBlocksFromPeer(peer)
case bpRequestTimeout: // unconditional timeout for each peer's request.
peer := bp.peers[event.peerId]
if peer == nil {
// cleanup was already handled.
return
}
height := event.height
request := peer.requests[height]
if request == nil || request.block != nil {
// the request was fulfilled by some peer or this peer.
return
}
// A request for peer timed out.
peer.bad++
if request.tries < maxTries {
log.Warn("Timeout: Trying again.", "tries", request.tries, "peerId", peer.id)
// try again.
select {
case bp.requestsCh <- BlockRequest{height, peer.id}:
request.startAndTimeoutTo(bp.eventsCh) // also bumps request.tries
default:
// The request cannot be made because requestCh is full.
// Just delete the request.
delete(peer.requests, height)
}
} else {
log.Warn("Timeout: Deleting request")
// delete the request.
delete(peer.requests, height)
blockInfo := bp.blockInfos[height]
if blockInfo != nil {
delete(blockInfo.requests, peer.id)
}
select {
case bp.timeoutsCh <- peer.id:
default:
}
}
}
}
// NOTE: This function is sufficient, but we should find pending blocks
// and sample the peers in one go rather than the current O(n^2) impl.
func (bp *BlockPool) requestBlocksFromRandomPeers(maxPeers int) {
chosen := bp.pickAvailablePeers(maxPeers)
log.Debug("requestBlocksFromRandomPeers", "chosen", len(chosen))
for _, peer := range chosen {
bp.requestBlocksFromPeer(peer)
}
}
func (bp *BlockPool) requestBlocksFromPeer(peer *bpPeer) {
// If peer is available and can provide something...
for height := bp.height; peer.available(); height++ {
blockInfo := bp.blockInfos[height]
if blockInfo == nil {
// We're out of range.
return
}
needsMorePeers := blockInfo.needsMorePeers()
alreadyAskedPeer := blockInfo.requests[peer.id] != nil
if needsMorePeers && !alreadyAskedPeer {
select {
case bp.requestsCh <- BlockRequest{height, peer.id}:
// Create a new request and start the timer.
request := &bpBlockRequest{
peer = &bpPeer{
height: height,
peer: peer,
id: peerId,
numRequests: 0,
}
blockInfo.requests[peer.id] = request
peer.requests[height] = request
request.startAndTimeoutTo(bp.eventsCh) // also bumps request.tries
default:
// The request cannot be made because requestCh is full.
// Just stop.
pool.peers[peerId] = peer
}
}
func (pool *BlockPool) RemovePeer(peerId string) {
pool.peersMtx.Lock() // Lock
defer pool.peersMtx.Unlock()
delete(pool.peers, peerId)
}
// Pick an available peer with at least the given minHeight.
// If no peers are available, returns nil.
func (pool *BlockPool) pickIncrAvailablePeer(minHeight uint) *bpPeer {
pool.peersMtx.Lock()
defer pool.peersMtx.Unlock()
for _, peer := range pool.peers {
if peer.numRequests >= maxRequestsPerPeer {
continue
}
if peer.height < minHeight {
continue
}
peer.numRequests++
return peer
}
return nil
}
func (pool *BlockPool) decrPeer(peerId string) {
pool.peersMtx.Lock()
defer pool.peersMtx.Unlock()
peer := pool.peers[peerId]
if peer == nil {
return
}
}
}
peer.numRequests--
}
func (bp *BlockPool) makeMoreBlockInfos() {
// make more requests if necessary.
for i := 0; i < requestBatchSize; i++ {
//log.Debug("Confused?",
// "numPending", bp.numPending, "maxPendingRequests", maxPendingRequests, "numtotal", bp.numTotal, "maxTotalRequests", maxTotalRequests)
if bp.numPending < maxPendingRequests && bp.numTotal < maxTotalRequests {
// Make a request for the next block height
requestHeight := bp.height + uint(bp.numTotal)
log.Debug("New blockInfo", "height", requestHeight)
blockInfo := bpNewBlockInfo(requestHeight)
bp.blockInfos[requestHeight] = blockInfo
bp.numPending++
bp.numTotal++
} else {
break
}
}
func (pool *BlockPool) nextHeight() uint {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
return pool.height + uint(pool.numTotal)
}
func (bp *BlockPool) pickAvailablePeers(choose int) []*bpPeer {
available := []*bpPeer{}
for _, peer := range bp.peers {
if peer.available() {
available = append(available, peer)
}
}
perm := rand.Perm(MinInt(choose, len(available)))
chosen := make([]*bpPeer, len(perm))
for i, idx := range perm {
chosen[i] = available[idx]
}
return chosen
}
func (pool *BlockPool) makeRequest(height uint) {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
// blocking
func (bp *BlockPool) pushBlocksFromStart() {
for height := bp.height; ; height++ {
// push block to blocksCh.
blockInfo := bp.blockInfos[height]
if blockInfo == nil || blockInfo.block == nil {
break
}
bp.numTotal--
bp.height++
delete(bp.blockInfos, height)
bp.blocksCh <- blockInfo.block
}
}
//-----------------------------------------------------------------------------
type bpBlockInfo struct {
height uint
requests map[string]*bpBlockRequest
block *types.Block // first block received
blockBy string // peerId of source
}
func bpNewBlockInfo(height uint) *bpBlockInfo {
return &bpBlockInfo{
request := &bpRequest{
height: height,
requests: make(map[string]*bpBlockRequest),
peerId: "",
block: nil,
}
}
pool.requests[height] = request
func (blockInfo *bpBlockInfo) needsMorePeers() bool {
return len(blockInfo.requests) < maxPeersPerRequest
}
//-------------------------------------
type bpBlockRequest struct {
peer *bpPeer
height uint
block *types.Block
tries int
}
// bump tries++ and set timeout.
// NOTE: the timer is unconditional.
func (request *bpBlockRequest) startAndTimeoutTo(eventsCh chan<- interface{}) {
request.tries++
time.AfterFunc(requestTimeoutSeconds*time.Second, func() {
eventsCh <- bpRequestTimeout{
peerId: request.peer.id,
height: request.height,
nextHeight := pool.height + uint(pool.numTotal)
if nextHeight == height {
pool.numTotal++
pool.numPending++
}
})
go requestRoutine(pool, height)
}
func (pool *BlockPool) sendRequest(height uint, peerId string) {
if atomic.LoadInt32(&pool.running) == 0 {
return
}
pool.requestsCh <- BlockRequest{height, peerId}
}
func (pool *BlockPool) sendTimeout(peerId string) {
if atomic.LoadInt32(&pool.running) == 0 {
return
}
pool.timeoutsCh <- peerId
}
func (pool *BlockPool) debug() string {
pool.requestsMtx.Lock() // Lock
defer pool.requestsMtx.Unlock()
str := ""
for h := pool.height; h < pool.height+uint(pool.numTotal); h++ {
if pool.requests[h] == nil {
str += Fmt("H(%v):X ", h)
} else {
str += Fmt("H(%v):", h)
str += Fmt("B?(%v) ", pool.requests[h].block != nil)
}
}
return str
}
//-------------------------------------
@ -317,38 +316,61 @@ func (request *bpBlockRequest) startAndTimeoutTo(eventsCh chan<- interface{}) {
type bpPeer struct {
id string
height uint
requests map[uint]*bpBlockRequest
// Count good/bad events from peer.
good uint
bad uint
numRequests int32
}
func bpNewPeer(peerId string, height uint) *bpPeer {
return &bpPeer{
id: peerId,
height: height,
requests: make(map[uint]*bpBlockRequest),
}
}
func (peer *bpPeer) available() bool {
return len(peer.requests) < maxOutstandingRequestsPerPeer
type bpRequest struct {
height uint
peerId string
block *types.Block
}
//-------------------------------------
// bp.eventsCh messages
type bpBlockResponse struct {
block *types.Block
peerId string
// Responsible for making more requests as necessary
// Returns when a block is found (e.g. AddBlock() is called)
func requestRoutine(pool *BlockPool, height uint) {
for {
var peer *bpPeer = nil
PICK_LOOP:
for {
if !pool.IsRunning() {
log.Debug("BlockPool not running. Stopping requestRoutine", "height", height)
return
}
peer = pool.pickIncrAvailablePeer(height)
if peer == nil {
//log.Debug("No peers available", "height", height)
time.Sleep(requestIntervalMS * time.Millisecond)
continue PICK_LOOP
}
break PICK_LOOP
}
pool.setPeerForRequest(height, peer.id)
for try := 0; try < maxTries; try++ {
pool.sendRequest(height, peer.id)
time.Sleep(requestTimeoutSeconds * time.Second)
if pool.hasBlock(height) {
pool.decrPeer(peer.id)
return
}
bpHeight, _, _ := pool.GetStatus()
if height < bpHeight {
pool.decrPeer(peer.id)
return
}
}
pool.RemovePeer(peer.id)
pool.sendTimeout(peer.id)
}
}
type bpPeerStatus struct {
peerId string
height uint // blockchain tip of peer
}
//-------------------------------------
type bpRequestTimeout struct {
peerId string
height uint
type BlockRequest struct {
Height uint
PeerId string
}

View File

@ -3,6 +3,7 @@ package blockchain
import (
"math/rand"
"testing"
"time"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/types"
@ -24,26 +25,34 @@ func makePeers(numPeers int, minHeight, maxHeight uint) map[string]testPeer {
}
func TestBasic(t *testing.T) {
// 100 peers anywhere at height 0 to 1000.
peers := makePeers(100, 0, 1000)
peers := makePeers(10, 0, 1000)
start := uint(42)
maxHeight := uint(300)
timeoutsCh := make(chan string, 100)
requestsCh := make(chan BlockRequest, 100)
blocksCh := make(chan *types.Block, 100)
pool := NewBlockPool(start, timeoutsCh, requestsCh, blocksCh)
pool := NewBlockPool(start, requestsCh, timeoutsCh)
pool.Start()
// Introduce each peer.
go func() {
for _, peer := range peers {
pool.SetPeerStatus(peer.id, peer.height)
pool.SetPeerHeight(peer.id, peer.height)
}
}()
lastSeenBlock := uint(41)
// Start a goroutine to pull blocks
go func() {
for {
if !pool.IsRunning() {
return
}
first, second := pool.PeekTwoBlocks()
if first != nil && second != nil {
pool.PopRequest()
} else {
time.Sleep(1 * time.Second)
}
}
}()
// Pull from channels
for {
@ -52,21 +61,15 @@ func TestBasic(t *testing.T) {
t.Errorf("timeout: %v", peerId)
case request := <-requestsCh:
log.Debug("TEST: Pulled new BlockRequest", "request", request)
// After a while, pretend like we got a block from the peer.
if request.Height == 300 {
return // Done!
}
// Request desired, pretend like we got the block immediately.
go func() {
block := &types.Block{Header: &types.Header{Height: request.Height}}
pool.AddBlock(block, request.PeerId)
log.Debug("TEST: Added block", "block", request.Height, "peer", request.PeerId)
}()
case block := <-blocksCh:
log.Debug("TEST: Pulled new Block", "height", block.Height)
if block.Height != lastSeenBlock+1 {
t.Fatalf("Wrong order of blocks seen. Expected: %v Got: %v", lastSeenBlock+1, block.Height)
}
lastSeenBlock++
if block.Height == maxHeight {
return // Done!
}
}
}
@ -74,39 +77,52 @@ func TestBasic(t *testing.T) {
}
func TestTimeout(t *testing.T) {
peers := makePeers(100, 0, 1000)
peers := makePeers(10, 0, 1000)
start := uint(42)
timeoutsCh := make(chan string, 10)
requestsCh := make(chan BlockRequest, 10)
blocksCh := make(chan *types.Block, 100)
pool := NewBlockPool(start, timeoutsCh, requestsCh, blocksCh)
timeoutsCh := make(chan string, 100)
requestsCh := make(chan BlockRequest, 100)
pool := NewBlockPool(start, requestsCh, timeoutsCh)
pool.Start()
// Introduce each peer.
go func() {
for _, peer := range peers {
pool.SetPeerStatus(peer.id, peer.height)
pool.SetPeerHeight(peer.id, peer.height)
}
}()
// Start a goroutine to pull blocks
go func() {
for {
if !pool.IsRunning() {
return
}
first, second := pool.PeekTwoBlocks()
if first != nil && second != nil {
pool.PopRequest()
} else {
time.Sleep(1 * time.Second)
}
}
}()
// Pull from channels
counter := 0
timedOut := map[string]struct{}{}
for {
select {
case peerId := <-timeoutsCh:
// Timed out. Done!
if peers[peerId].id != peerId {
t.Errorf("Unexpected peer from timeoutsCh")
log.Debug("Timeout", "peerId", peerId)
if _, ok := timedOut[peerId]; !ok {
counter++
if counter == len(peers) {
return // Done!
}
return
case _ = <-requestsCh:
// Don't do anything, let it time out.
case _ = <-blocksCh:
t.Errorf("Got block when none expected")
return
}
case request := <-requestsCh:
log.Debug("TEST: Pulled new BlockRequest", "request", request)
}
}
pool.Stop()
}

304
blockchain/reactor.go Normal file
View File

@ -0,0 +1,304 @@
package blockchain
import (
"bytes"
"errors"
"fmt"
"sync/atomic"
"time"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/p2p"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
const (
BlockchainChannel = byte(0x40)
defaultChannelCapacity = 100
defaultSleepIntervalMS = 500
trySyncIntervalMS = 100
// stop syncing when last block's time is
// within this much of the system time.
stopSyncingDurationMinutes = 10
)
type stateResetter interface {
ResetToState(*sm.State)
}
// BlockchainReactor handles long-term catchup syncing.
type BlockchainReactor struct {
sw *p2p.Switch
state *sm.State
store *BlockStore
pool *BlockPool
sync bool
requestsCh chan BlockRequest
timeoutsCh chan string
lastBlock *types.Block
quit chan struct{}
running uint32
}
func NewBlockchainReactor(state *sm.State, store *BlockStore, sync bool) *BlockchainReactor {
if state.LastBlockHeight != store.Height() {
panic(Fmt("state (%v) and store (%v) height mismatch", state.LastBlockHeight, store.Height()))
}
requestsCh := make(chan BlockRequest, defaultChannelCapacity)
timeoutsCh := make(chan string, defaultChannelCapacity)
pool := NewBlockPool(
store.Height()+1,
requestsCh,
timeoutsCh,
)
bcR := &BlockchainReactor{
state: state,
store: store,
pool: pool,
sync: sync,
requestsCh: requestsCh,
timeoutsCh: timeoutsCh,
quit: make(chan struct{}),
running: uint32(0),
}
return bcR
}
// Implements Reactor
func (bcR *BlockchainReactor) Start(sw *p2p.Switch) {
if atomic.CompareAndSwapUint32(&bcR.running, 0, 1) {
log.Info("Starting BlockchainReactor")
bcR.sw = sw
bcR.pool.Start()
if bcR.sync {
go bcR.poolRoutine()
}
}
}
// Implements Reactor
func (bcR *BlockchainReactor) Stop() {
if atomic.CompareAndSwapUint32(&bcR.running, 1, 0) {
log.Info("Stopping BlockchainReactor")
close(bcR.quit)
bcR.pool.Stop()
}
}
// Implements Reactor
func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
return []*p2p.ChannelDescriptor{
&p2p.ChannelDescriptor{
Id: BlockchainChannel,
Priority: 5,
SendQueueCapacity: 100,
},
}
}
// Implements Reactor
func (bcR *BlockchainReactor) AddPeer(peer *p2p.Peer) {
// Send peer our state.
peer.Send(BlockchainChannel, bcPeerStatusMessage{bcR.store.Height()})
}
// Implements Reactor
func (bcR *BlockchainReactor) RemovePeer(peer *p2p.Peer, reason interface{}) {
// Remove peer from the pool.
bcR.pool.RemovePeer(peer.Key)
}
// Implements Reactor
func (bcR *BlockchainReactor) Receive(chId byte, src *p2p.Peer, msgBytes []byte) {
_, msg_, err := DecodeMessage(msgBytes)
if err != nil {
log.Warn("Error decoding message", "error", err)
return
}
log.Info("Received message", "msg", msg_)
switch msg := msg_.(type) {
case bcBlockRequestMessage:
// Got a request for a block. Respond with block if we have it.
block := bcR.store.LoadBlock(msg.Height)
if block != nil {
msg := bcBlockResponseMessage{Block: block}
queued := src.TrySend(BlockchainChannel, msg)
if !queued {
// queue is full, just ignore.
}
} else {
// TODO peer is asking for things we don't have.
}
case bcBlockResponseMessage:
// Got a block.
bcR.pool.AddBlock(msg.Block, src.Key)
case bcPeerStatusMessage:
// Got a peer status.
bcR.pool.SetPeerHeight(src.Key, msg.Height)
default:
// Ignore unknown message
}
}
// Handle messages from the poolReactor telling the reactor what to do.
func (bcR *BlockchainReactor) poolRoutine() {
trySyncTicker := time.NewTicker(trySyncIntervalMS * time.Millisecond)
FOR_LOOP:
for {
select {
case request := <-bcR.requestsCh: // chan BlockRequest
peer := bcR.sw.Peers().Get(request.PeerId)
if peer == nil {
// We can't fulfill the request.
continue FOR_LOOP
}
msg := bcBlockRequestMessage{request.Height}
queued := peer.TrySend(BlockchainChannel, msg)
if !queued {
// We couldn't queue the request.
time.Sleep(defaultSleepIntervalMS * time.Millisecond)
continue FOR_LOOP
}
case peerId := <-bcR.timeoutsCh: // chan string
// Peer timed out.
peer := bcR.sw.Peers().Get(peerId)
if peer != nil {
bcR.sw.StopPeerForError(peer, errors.New("BlockchainReactor Timeout"))
}
case _ = <-trySyncTicker.C: // chan time
//var lastValidatedBlock *types.Block
SYNC_LOOP:
for i := 0; i < 10; i++ {
// See if there are any blocks to sync.
first, second := bcR.pool.PeekTwoBlocks()
//log.Debug("TrySync peeked", "first", first, "second", second)
if first == nil || second == nil {
// We need both to sync the first block.
break SYNC_LOOP
}
firstParts := first.MakePartSet()
firstPartsHeader := firstParts.Header()
// Finally, verify the first block using the second's validation.
err := bcR.state.BondedValidators.VerifyValidation(
first.Hash(), firstPartsHeader, first.Height, second.Validation)
if err != nil {
log.Debug("error in validation", "error", err)
bcR.pool.RedoRequest(first.Height)
break SYNC_LOOP
} else {
bcR.pool.PopRequest()
err := sm.ExecBlock(bcR.state, first, firstPartsHeader)
if err != nil {
// TODO This is bad, are we zombie?
panic(Fmt("Failed to process committed block: %v", err))
}
bcR.store.SaveBlock(first, firstParts, second.Validation)
bcR.state.Save()
//lastValidatedBlock = first
}
}
/*
// We're done syncing for now (will do again shortly)
// See if we want to stop syncing and turn on the
// consensus reactor.
// TODO: use other heuristics too besides blocktime.
// It's not a security concern, as it only needs to happen
// upon node sync, and there's also a second (slower)
// method of syncing in the consensus reactor.
if lastValidatedBlock != nil && time.Now().Sub(lastValidatedBlock.Time) < stopSyncingDurationMinutes*time.Minute {
go func() {
log.Info("Stopping blockpool syncing, turning on consensus...")
trySyncTicker.Stop() // Just stop the block requests. Still serve blocks to others.
conR := bcR.sw.Reactor("CONSENSUS")
conR.(stateResetter).ResetToState(bcR.state)
conR.Start(bcR.sw)
for _, peer := range bcR.sw.Peers().List() {
conR.AddPeer(peer)
}
}()
break FOR_LOOP
}
*/
continue FOR_LOOP
case <-bcR.quit:
break FOR_LOOP
}
}
}
func (bcR *BlockchainReactor) BroadcastStatus() error {
bcR.sw.Broadcast(BlockchainChannel, bcPeerStatusMessage{bcR.store.Height()})
return nil
}
//-----------------------------------------------------------------------------
// Messages
const (
msgTypeUnknown = byte(0x00)
msgTypeBlockRequest = byte(0x10)
msgTypeBlockResponse = byte(0x11)
msgTypePeerStatus = byte(0x20)
)
// TODO: check for unnecessary extra bytes at the end.
func DecodeMessage(bz []byte) (msgType byte, msg interface{}, err error) {
n := new(int64)
msgType = bz[0]
r := bytes.NewReader(bz)
switch msgType {
case msgTypeBlockRequest:
msg = binary.ReadBinary(bcBlockRequestMessage{}, r, n, &err)
case msgTypeBlockResponse:
msg = binary.ReadBinary(bcBlockResponseMessage{}, r, n, &err)
case msgTypePeerStatus:
msg = binary.ReadBinary(bcPeerStatusMessage{}, r, n, &err)
default:
msg = nil
}
return
}
//-------------------------------------
type bcBlockRequestMessage struct {
Height uint
}
func (m bcBlockRequestMessage) TypeByte() byte { return msgTypeBlockRequest }
func (m bcBlockRequestMessage) String() string {
return fmt.Sprintf("[bcBlockRequestMessage %v]", m.Height)
}
//-------------------------------------
type bcBlockResponseMessage struct {
Block *types.Block
}
func (m bcBlockResponseMessage) TypeByte() byte { return msgTypeBlockResponse }
func (m bcBlockResponseMessage) String() string {
return fmt.Sprintf("[bcBlockResponseMessage %v]", m.Block.Height)
}
//-------------------------------------
type bcPeerStatusMessage struct {
Height uint
}
func (m bcPeerStatusMessage) TypeByte() byte { return msgTypePeerStatus }
func (m bcPeerStatusMessage) String() string {
return fmt.Sprintf("[bcPeerStatusMessage %v]", m.Height)
}

View File

@ -57,7 +57,7 @@ func (bs *BlockStore) LoadBlock(height uint) *types.Block {
if r == nil {
panic(Fmt("Block does not exist at height %v", height))
}
meta := binary.ReadBinary(&BlockMeta{}, r, &n, &err).(*BlockMeta)
meta := binary.ReadBinary(&types.BlockMeta{}, r, &n, &err).(*types.BlockMeta)
if err != nil {
panic(Fmt("Error reading block meta: %v", err))
}
@ -87,14 +87,14 @@ func (bs *BlockStore) LoadBlockPart(height uint, index uint) *types.Part {
return part
}
func (bs *BlockStore) LoadBlockMeta(height uint) *BlockMeta {
func (bs *BlockStore) LoadBlockMeta(height uint) *types.BlockMeta {
var n int64
var err error
r := bs.GetReader(calcBlockMetaKey(height))
if r == nil {
panic(Fmt("BlockMeta does not exist for height %v", height))
}
meta := binary.ReadBinary(&BlockMeta{}, r, &n, &err).(*BlockMeta)
meta := binary.ReadBinary(&types.BlockMeta{}, r, &n, &err).(*types.BlockMeta)
if err != nil {
panic(Fmt("Error reading block meta: %v", err))
}
@ -150,7 +150,7 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s
}
// Save block meta
meta := makeBlockMeta(block, blockParts)
meta := types.NewBlockMeta(block, blockParts)
metaBytes := binary.BinaryBytes(meta)
bs.db.Set(calcBlockMetaKey(height), metaBytes)
@ -184,22 +184,6 @@ func (bs *BlockStore) saveBlockPart(height uint, index uint, part *types.Part) {
//-----------------------------------------------------------------------------
type BlockMeta struct {
Hash []byte // The block hash
Header *types.Header // The block's Header
Parts types.PartSetHeader // The PartSetHeader, for transfer
}
func makeBlockMeta(block *types.Block, blockParts *types.PartSet) *BlockMeta {
return &BlockMeta{
Hash: block.Hash(),
Header: block.Header,
Parts: blockParts.Header(),
}
}
//-----------------------------------------------------------------------------
func calcBlockMetaKey(height uint) []byte {
return []byte(fmt.Sprintf("H:%v", height))
}

View File

@ -1,6 +1,7 @@
package common
import (
"encoding/binary"
"sort"
)
@ -18,3 +19,13 @@ func SearchUint64s(a []uint64, x uint64) int {
}
func (p Uint64Slice) Search(x uint64) int { return SearchUint64s(p, x) }
//-----------------------------------------------------------------------------
func PutUint64(dest []byte, i uint64) {
binary.LittleEndian.PutUint64(dest, i)
}
func GetUint64(src []byte) uint64 {
return binary.LittleEndian.Uint64(src)
}

View File

@ -1,44 +1,65 @@
package common
import "time"
import "sync"
/*
RepeatTimer repeatedly sends a struct{}{} to .Ch after each "dur" period.
It's good for keeping connections alive.
*/
type RepeatTimer struct {
Name string
Ch chan struct{}
Ch chan time.Time
mtx sync.Mutex
name string
ticker *time.Ticker
quit chan struct{}
dur time.Duration
timer *time.Timer
}
func NewRepeatTimer(name string, dur time.Duration) *RepeatTimer {
var ch = make(chan struct{})
var quit = make(chan struct{})
var t = &RepeatTimer{Name: name, Ch: ch, dur: dur, quit: quit}
t.timer = time.AfterFunc(dur, t.fireRoutine)
var t = &RepeatTimer{
Ch: make(chan time.Time),
ticker: time.NewTicker(dur),
quit: make(chan struct{}),
name: name,
dur: dur,
}
go t.fireRoutine(t.ticker)
return t
}
func (t *RepeatTimer) fireRoutine() {
func (t *RepeatTimer) fireRoutine(ticker *time.Ticker) {
for {
select {
case t.Ch <- struct{}{}:
t.timer.Reset(t.dur)
case t_ := <-ticker.C:
t.Ch <- t_
case <-t.quit:
// do nothing
default:
t.timer.Reset(t.dur)
return
}
}
}
// Wait the duration again before firing.
func (t *RepeatTimer) Reset() {
t.timer.Reset(t.dur)
t.mtx.Lock() // Lock
defer t.mtx.Unlock()
if t.ticker != nil {
t.ticker.Stop()
}
t.ticker = time.NewTicker(t.dur)
go t.fireRoutine(t.ticker)
}
func (t *RepeatTimer) Stop() bool {
close(t.quit)
return t.timer.Stop()
t.mtx.Lock() // Lock
defer t.mtx.Unlock()
exists := t.ticker != nil
if exists {
t.ticker.Stop()
t.ticker = nil
}
return exists
}

78
common/word.go Normal file
View File

@ -0,0 +1,78 @@
package common
import (
"bytes"
"encoding/binary"
"sort"
)
var (
Zero256 = Word256{0}
One256 = Word256{1}
)
type Word256 [32]byte
func (w Word256) String() string { return string(w[:]) }
func (w Word256) Copy() Word256 { return w }
func (w Word256) Bytes() []byte { return w[:] } // copied.
func (w Word256) Prefix(n int) []byte { return w[:n] }
func (w Word256) IsZero() bool {
accum := byte(0)
for _, byt := range w {
accum |= byt
}
return accum == 0
}
func (w Word256) Compare(other Word256) int {
return bytes.Compare(w[:], other[:])
}
func Uint64ToWord256(i uint64) Word256 {
word := Word256{}
PutUint64(word[:], i)
return word
}
func RightPadWord256(bz []byte) (word Word256) {
copy(word[:], bz)
return
}
func LeftPadWord256(bz []byte) (word Word256) {
copy(word[32-len(bz):], bz)
return
}
func Uint64FromWord256(word Word256) uint64 {
return binary.LittleEndian.Uint64(word[:])
}
//-------------------------------------
type Tuple256 struct {
First Word256
Second Word256
}
func (tuple Tuple256) Compare(other Tuple256) int {
firstCompare := tuple.First.Compare(other.First)
if firstCompare == 0 {
return tuple.Second.Compare(other.Second)
} else {
return firstCompare
}
}
func Tuple256Split(t Tuple256) (Word256, Word256) {
return t.First, t.Second
}
type Tuple256Slice []Tuple256
func (p Tuple256Slice) Len() int { return len(p) }
func (p Tuple256Slice) Less(i, j int) bool {
return p[i].Compare(p[j]) < 0
}
func (p Tuple256Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Tuple256Slice) Sort() { sort.Sort(p) }

View File

@ -104,6 +104,8 @@ func initDefaults(rootDir string) {
app.SetDefault("GenesisFile", rootDir+"/genesis.json")
app.SetDefault("AddrBookFile", rootDir+"/addrbook.json")
app.SetDefault("PrivValidatorfile", rootDir+"/priv_validator.json")
app.SetDefault("FastSync", false)
}
func Init(rootDir string) {
@ -161,6 +163,7 @@ func ParseFlags(args []string) {
flags.BoolVar(&printHelp, "help", false, "Print this help message.")
flags.String("listen_addr", app.GetString("ListenAddr"), "Listen address. (0.0.0.0:0 means any interface, any port)")
flags.String("seed_node", app.GetString("SeedNode"), "Address of seed node")
flags.Bool("fast_sync", app.GetBool("FastSync"), "Fast blockchain syncing")
flags.String("rpc_http_listen_addr", app.GetString("RPC.HTTP.ListenAddr"), "RPC listen address. Port required")
flags.Parse(args)
if printHelp {
@ -171,6 +174,7 @@ func ParseFlags(args []string) {
// Merge parsed flag values onto app.
app.BindPFlag("ListenAddr", flags.Lookup("listen_addr"))
app.BindPFlag("SeedNode", flags.Lookup("seed_node"))
app.BindPFlag("FastSync", flags.Lookup("fast_sync"))
app.BindPFlag("RPC.HTTP.ListenAddr", flags.Lookup("rpc_http_listen_addr"))
// Confused?

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
@ -94,3 +95,7 @@ func (pol *POL) StringShort() string {
Fingerprint(pol.BlockHash), pol.BlockParts)
}
}
func (pol *POL) MakePartSet() *types.PartSet {
return types.NewPartSetFromData(binary.BinaryBytes(pol))
}

View File

@ -9,6 +9,7 @@ import (
"time"
"github.com/tendermint/tendermint/binary"
bc "github.com/tendermint/tendermint/blockchain"
. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/consensus/types"
"github.com/tendermint/tendermint/p2p"
@ -17,9 +18,9 @@ import (
)
const (
StateCh = byte(0x20)
DataCh = byte(0x21)
VoteCh = byte(0x22)
StateChannel = byte(0x20)
DataChannel = byte(0x21)
VoteChannel = byte(0x22)
peerStateKey = "ConsensusReactor.peerState"
@ -28,17 +29,18 @@ const (
//-----------------------------------------------------------------------------
// The reactor's underlying ConsensusState may change state at any time.
// We atomically copy the RoundState struct before using it.
type ConsensusReactor struct {
sw *p2p.Switch
started uint32
stopped uint32
running uint32
quit chan struct{}
blockStore *types.BlockStore
blockStore *bc.BlockStore
conS *ConsensusState
}
func NewConsensusReactor(consensusState *ConsensusState, blockStore *types.BlockStore) *ConsensusReactor {
func NewConsensusReactor(consensusState *ConsensusState, blockStore *bc.BlockStore) *ConsensusReactor {
conR := &ConsensusReactor{
blockStore: blockStore,
quit: make(chan struct{}),
@ -49,7 +51,7 @@ func NewConsensusReactor(consensusState *ConsensusState, blockStore *types.Block
// Implements Reactor
func (conR *ConsensusReactor) Start(sw *p2p.Switch) {
if atomic.CompareAndSwapUint32(&conR.started, 0, 1) {
if atomic.CompareAndSwapUint32(&conR.running, 0, 1) {
log.Info("Starting ConsensusReactor")
conR.sw = sw
conR.conS.Start()
@ -59,15 +61,15 @@ func (conR *ConsensusReactor) Start(sw *p2p.Switch) {
// Implements Reactor
func (conR *ConsensusReactor) Stop() {
if atomic.CompareAndSwapUint32(&conR.stopped, 0, 1) {
if atomic.CompareAndSwapUint32(&conR.running, 1, 0) {
log.Info("Stopping ConsensusReactor")
conR.conS.Stop()
close(conR.quit)
}
}
func (conR *ConsensusReactor) IsStopped() bool {
return atomic.LoadUint32(&conR.stopped) == 1
func (conR *ConsensusReactor) IsRunning() bool {
return atomic.LoadUint32(&conR.running) == 1
}
// Implements Reactor
@ -75,15 +77,15 @@ func (conR *ConsensusReactor) GetChannels() []*p2p.ChannelDescriptor {
// TODO optimize
return []*p2p.ChannelDescriptor{
&p2p.ChannelDescriptor{
Id: StateCh,
Id: StateChannel,
Priority: 5,
},
&p2p.ChannelDescriptor{
Id: DataCh,
Id: DataChannel,
Priority: 5,
},
&p2p.ChannelDescriptor{
Id: VoteCh,
Id: VoteChannel,
Priority: 5,
},
}
@ -91,6 +93,10 @@ func (conR *ConsensusReactor) GetChannels() []*p2p.ChannelDescriptor {
// Implements Reactor
func (conR *ConsensusReactor) AddPeer(peer *p2p.Peer) {
if !conR.IsRunning() {
return
}
// Create peerState for peer
peerState := NewPeerState(peer)
peer.Data.Set(peerStateKey, peerState)
@ -105,11 +111,18 @@ func (conR *ConsensusReactor) AddPeer(peer *p2p.Peer) {
// Implements Reactor
func (conR *ConsensusReactor) RemovePeer(peer *p2p.Peer, reason interface{}) {
if !conR.IsRunning() {
return
}
//peer.Data.Get(peerStateKey).(*PeerState).Disconnect()
}
// Implements Reactor
func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte) {
if !conR.IsRunning() {
return
}
// Get round state
rs := conR.conS.GetRoundState()
@ -122,7 +135,7 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte
log.Debug("Receive", "channel", chId, "peer", peer, "msg", msg_, "bytes", msgBytes)
switch chId {
case StateCh:
case StateChannel:
switch msg := msg_.(type) {
case *NewRoundStepMessage:
ps.ApplyNewRoundStepMessage(msg, rs)
@ -134,7 +147,7 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte
// Ignore unknown message
}
case DataCh:
case DataChannel:
switch msg := msg_.(type) {
case *Proposal:
ps.SetHasProposal(msg)
@ -155,7 +168,7 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte
// Ignore unknown message
}
case VoteCh:
case VoteChannel:
switch msg := msg_.(type) {
case *VoteMessage:
vote := msg.Vote
@ -192,7 +205,7 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte
Type: vote.Type,
Index: index,
}
conR.sw.Broadcast(StateCh, msg)
conR.sw.Broadcast(StateChannel, msg)
}
default:
@ -212,6 +225,11 @@ func (conR *ConsensusReactor) SetPrivValidator(priv *sm.PrivValidator) {
conR.conS.SetPrivValidator(priv)
}
// Reset to some state.
func (conR *ConsensusReactor) ResetToState(state *sm.State) {
conR.conS.updateToState(state, false)
}
//--------------------------------------
func makeRoundStepMessages(rs *RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) {
@ -252,10 +270,10 @@ func (conR *ConsensusReactor) broadcastNewRoundStepRoutine() {
nrsMsg, csMsg := makeRoundStepMessages(rs)
if nrsMsg != nil {
conR.sw.Broadcast(StateCh, nrsMsg)
conR.sw.Broadcast(StateChannel, nrsMsg)
}
if csMsg != nil {
conR.sw.Broadcast(StateCh, csMsg)
conR.sw.Broadcast(StateChannel, csMsg)
}
}
}
@ -264,10 +282,10 @@ func (conR *ConsensusReactor) sendNewRoundStepRoutine(peer *p2p.Peer) {
rs := conR.conS.GetRoundState()
nrsMsg, csMsg := makeRoundStepMessages(rs)
if nrsMsg != nil {
peer.Send(StateCh, nrsMsg)
peer.Send(StateChannel, nrsMsg)
}
if csMsg != nil {
peer.Send(StateCh, nrsMsg)
peer.Send(StateChannel, nrsMsg)
}
}
@ -276,7 +294,7 @@ func (conR *ConsensusReactor) gossipDataRoutine(peer *p2p.Peer, ps *PeerState) {
OUTER_LOOP:
for {
// Manage disconnects from self or peer.
if peer.IsStopped() || conR.IsStopped() {
if !peer.IsRunning() || !conR.IsRunning() {
log.Info(Fmt("Stopping gossipDataRoutine for %v.", peer))
return
}
@ -296,7 +314,7 @@ OUTER_LOOP:
Type: partTypeProposalBlock,
Part: part,
}
peer.Send(DataCh, msg)
peer.Send(DataChannel, msg)
ps.SetHasProposalBlockPart(rs.Height, rs.Round, index)
continue OUTER_LOOP
}
@ -306,7 +324,7 @@ OUTER_LOOP:
if 0 < prs.Height && prs.Height < rs.Height {
//log.Debug("Data catchup", "height", rs.Height, "peerHeight", prs.Height, "peerProposalBlockBitArray", prs.ProposalBlockBitArray)
if index, ok := prs.ProposalBlockBitArray.Not().PickRandom(); ok {
// Ensure that the peer's PartSetHeaeder is correct
// Ensure that the peer's PartSetHeader is correct
blockMeta := conR.blockStore.LoadBlockMeta(prs.Height)
if !blockMeta.Parts.Equals(prs.ProposalBlockParts) {
log.Debug("Peer ProposalBlockParts mismatch, sleeping",
@ -329,7 +347,7 @@ OUTER_LOOP:
Type: partTypeProposalBlock,
Part: part,
}
peer.Send(DataCh, msg)
peer.Send(DataChannel, msg)
ps.SetHasProposalBlockPart(prs.Height, prs.Round, index)
continue OUTER_LOOP
} else {
@ -349,7 +367,7 @@ OUTER_LOOP:
// Send proposal?
if rs.Proposal != nil && !prs.Proposal {
msg := p2p.TypedMessage{msgTypeProposal, rs.Proposal}
peer.Send(DataCh, msg)
peer.Send(DataChannel, msg)
ps.SetHasProposal(rs.Proposal)
continue OUTER_LOOP
}
@ -363,7 +381,7 @@ OUTER_LOOP:
Type: partTypeProposalPOL,
Part: rs.ProposalPOLParts.GetPart(index),
}
peer.Send(DataCh, msg)
peer.Send(DataChannel, msg)
ps.SetHasProposalPOLPart(rs.Height, rs.Round, index)
continue OUTER_LOOP
}
@ -379,7 +397,7 @@ func (conR *ConsensusReactor) gossipVotesRoutine(peer *p2p.Peer, ps *PeerState)
OUTER_LOOP:
for {
// Manage disconnects from self or peer.
if peer.IsStopped() || conR.IsStopped() {
if !peer.IsRunning() || !conR.IsRunning() {
log.Info(Fmt("Stopping gossipVotesRoutine for %v.", peer))
return
}
@ -397,7 +415,7 @@ OUTER_LOOP:
vote := voteSet.GetByIndex(index)
// NOTE: vote may be a commit.
msg := &VoteMessage{index, vote}
peer.Send(VoteCh, msg)
peer.Send(VoteChannel, msg)
ps.SetHasVote(vote, index)
return true
}
@ -421,7 +439,7 @@ OUTER_LOOP:
Signature: commit.Signature,
}
msg := &VoteMessage{index, vote}
peer.Send(VoteCh, msg)
peer.Send(VoteChannel, msg)
ps.SetHasVote(vote, index)
return true
}

View File

@ -62,6 +62,7 @@ import (
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
bc "github.com/tendermint/tendermint/blockchain"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/config"
. "github.com/tendermint/tendermint/consensus/types"
@ -234,7 +235,7 @@ type ConsensusState struct {
stopped uint32
quit chan struct{}
blockStore *types.BlockStore
blockStore *bc.BlockStore
mempoolReactor *mempl.MempoolReactor
runActionCh chan RoundAction
newStepCh chan *RoundState
@ -247,7 +248,7 @@ type ConsensusState struct {
lastCommitVoteHeight uint // Last called commitVoteBlock() or saveCommitVoteBlock() on.
}
func NewConsensusState(state *sm.State, blockStore *types.BlockStore, mempoolReactor *mempl.MempoolReactor) *ConsensusState {
func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReactor *mempl.MempoolReactor) *ConsensusState {
cs := &ConsensusState{
quit: make(chan struct{}),
blockStore: blockStore,
@ -255,7 +256,7 @@ func NewConsensusState(state *sm.State, blockStore *types.BlockStore, mempoolRea
runActionCh: make(chan RoundAction, 1),
newStepCh: make(chan *RoundState, 1),
}
cs.updateToState(state)
cs.updateToState(state, true)
return cs
}
@ -456,9 +457,9 @@ ACTION_LOOP:
// If calculated round is greater than 0 (based on BlockTime or calculated StartTime)
// then also sets up the appropriate round, and cs.Step becomes RoundStepNewRound.
// Otherwise the round is 0 and cs.Step becomes RoundStepNewHeight.
func (cs *ConsensusState) updateToState(state *sm.State) {
func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) {
// Sanity check state.
if cs.Height > 0 && cs.Height != state.LastBlockHeight {
if contiguous && cs.Height > 0 && cs.Height != state.LastBlockHeight {
panic(Fmt("updateToState() expected state height of %v but found %v",
cs.Height, state.LastBlockHeight))
}
@ -466,6 +467,8 @@ func (cs *ConsensusState) updateToState(state *sm.State) {
// Reset fields based on state.
validators := state.BondedValidators
height := state.LastBlockHeight + 1 // next desired block height
// RoundState fields
cs.Height = height
cs.Round = 0
cs.Step = RoundStepNewHeight
@ -641,12 +644,12 @@ func (cs *ConsensusState) RunActionPropose(height uint, round uint) {
return
}
blockParts = types.NewPartSetFromData(binary.BinaryBytes(block))
blockParts = block.MakePartSet()
pol = cs.LockedPOL // If exists, is a PoUnlock.
}
if pol != nil {
polParts = types.NewPartSetFromData(binary.BinaryBytes(pol))
polParts = pol.MakePartSet()
}
// Make proposal
@ -856,7 +859,7 @@ func (cs *ConsensusState) TryFinalizeCommit(height uint) bool {
// We have the block, so save/stage/sign-commit-vote.
cs.saveCommitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Commits)
// Increment height.
cs.updateToState(cs.stagedState)
cs.updateToState(cs.stagedState, true)
// cs.Step is now RoundStepNewHeight or RoundStepNewRound
cs.newStepCh <- cs.getRoundState()
return true
@ -1012,7 +1015,8 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS
}
// Already staged?
if cs.stagedBlock == block {
blockHash := block.Hash()
if cs.stagedBlock != nil && len(blockHash) != 0 && bytes.Equal(cs.stagedBlock.Hash(), blockHash) {
return nil
}
@ -1021,7 +1025,7 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS
// Commit block onto the copied state.
// NOTE: Basic validation is done in state.AppendBlock().
err := stateCopy.AppendBlock(block, blockParts.Header())
err := sm.ExecBlock(stateCopy, block, blockParts.Header())
if err != nil {
return err
} else {

View File

@ -3,15 +3,15 @@ package consensus
import (
"sort"
bc "github.com/tendermint/tendermint/blockchain"
dbm "github.com/tendermint/tendermint/db"
mempl "github.com/tendermint/tendermint/mempool"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
func randConsensusState() (*ConsensusState, []*sm.PrivValidator) {
state, _, privValidators := sm.RandGenesisState(20, false, 1000, 10, false, 1000)
blockStore := types.NewBlockStore(dbm.NewMemDB())
blockStore := bc.NewBlockStore(dbm.NewMemDB())
mempool := mempl.NewMempool(state)
mempoolReactor := mempl.NewMempoolReactor(mempool)
cs := NewConsensusState(state, blockStore, mempoolReactor)

View File

@ -34,7 +34,7 @@ type VoteSet struct {
maj23Exists bool
}
// Constructs a new VoteSet struct used to accumulate votes for each round.
// Constructs a new VoteSet struct used to accumulate votes for given height/round.
func NewVoteSet(height uint, round uint, type_ byte, valSet *sm.ValidatorSet) *VoteSet {
if height == 0 {
panic("Cannot make VoteSet for height == 0, doesn't make sense.")

View File

@ -4,6 +4,7 @@ import (
"os"
"os/signal"
bc "github.com/tendermint/tendermint/blockchain"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/consensus"
@ -13,15 +14,15 @@ import (
"github.com/tendermint/tendermint/rpc"
"github.com/tendermint/tendermint/rpc/core"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
type Node struct {
lz []p2p.Listener
sw *p2p.Switch
book *p2p.AddrBook
blockStore *bc.BlockStore
pexReactor *p2p.PEXReactor
blockStore *types.BlockStore
bcReactor *bc.BlockchainReactor
mempoolReactor *mempl.MempoolReactor
consensusState *consensus.ConsensusState
consensusReactor *consensus.ConsensusReactor
@ -31,7 +32,7 @@ type Node struct {
func NewNode() *Node {
// Get BlockStore
blockStoreDB := dbm.GetDB("blockstore")
blockStore := types.NewBlockStore(blockStoreDB)
blockStore := bc.NewBlockStore(blockStoreDB)
// Get State
stateDB := dbm.GetDB("state")
@ -54,6 +55,9 @@ func NewNode() *Node {
book := p2p.NewAddrBook(config.App().GetString("AddrBookFile"))
pexReactor := p2p.NewPEXReactor(book)
// Get BlockchainReactor
bcReactor := bc.NewBlockchainReactor(state, blockStore, config.App().GetBool("FastSync"))
// Get MempoolReactor
mempool := mempl.NewMempool(state.Copy())
mempoolReactor := mempl.NewMempoolReactor(mempool)
@ -65,14 +69,23 @@ func NewNode() *Node {
consensusReactor.SetPrivValidator(privValidator)
}
sw := p2p.NewSwitch([]p2p.Reactor{pexReactor, mempoolReactor, consensusReactor})
sw.SetChainId(state.Hash(), config.App().GetString("Network"))
sw := p2p.NewSwitch()
sw.SetNetwork(config.App().GetString("Network"))
sw.AddReactor("PEX", pexReactor).Start(sw)
sw.AddReactor("MEMPOOL", mempoolReactor).Start(sw)
sw.AddReactor("BLOCKCHAIN", bcReactor).Start(sw)
if !config.App().GetBool("FastSync") {
sw.AddReactor("CONSENSUS", consensusReactor).Start(sw)
} else {
sw.AddReactor("CONSENSUS", consensusReactor)
}
return &Node{
sw: sw,
book: book,
pexReactor: pexReactor,
blockStore: blockStore,
pexReactor: pexReactor,
bcReactor: bcReactor,
mempoolReactor: mempoolReactor,
consensusState: consensusState,
consensusReactor: consensusReactor,
@ -86,7 +99,7 @@ func (n *Node) Start() {
go n.inboundConnectionRoutine(l)
}
n.book.Start()
n.sw.Start()
//n.sw.StartReactors()
}
func (n *Node) Stop() {

View File

@ -19,12 +19,14 @@ import (
type Mempool struct {
mtx sync.Mutex
state *sm.State
cache *sm.BlockCache
txs []types.Tx
}
func NewMempool(state *sm.State) *Mempool {
return &Mempool{
state: state,
cache: sm.NewBlockCache(state),
}
}
@ -36,7 +38,7 @@ func (mem *Mempool) GetState() *sm.State {
func (mem *Mempool) AddTx(tx types.Tx) (err error) {
mem.mtx.Lock()
defer mem.mtx.Unlock()
err = mem.state.ExecTx(tx, false)
err = sm.ExecTx(mem.cache, tx, false)
if err != nil {
log.Debug("AddTx() error", "tx", tx, "error", err)
return err
@ -62,6 +64,7 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) {
mem.mtx.Lock()
defer mem.mtx.Unlock()
mem.state = state.Copy()
mem.cache = sm.NewBlockCache(mem.state)
// First, create a lookup map of txns in new block.
blockTxsMap := make(map[string]struct{})
@ -86,7 +89,7 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) {
// Next, filter all txs that aren't valid given new state.
validTxs := []types.Tx{}
for _, tx := range txs {
err := mem.state.ExecTx(tx, false)
err := sm.ExecTx(mem.cache, tx, false)
if err == nil {
log.Debug("Filter in, valid", "tx", tx)
validTxs = append(validTxs, tx)

View File

@ -11,7 +11,7 @@ import (
)
var (
MempoolCh = byte(0x30)
MempoolChannel = byte(0x30)
)
// MempoolReactor handles mempool tx broadcasting amongst peers.
@ -52,7 +52,7 @@ func (memR *MempoolReactor) Stop() {
func (memR *MempoolReactor) GetChannels() []*p2p.ChannelDescriptor {
return []*p2p.ChannelDescriptor{
&p2p.ChannelDescriptor{
Id: MempoolCh,
Id: MempoolChannel,
Priority: 5,
},
}
@ -92,7 +92,7 @@ func (memR *MempoolReactor) Receive(chId byte, src *p2p.Peer, msgBytes []byte) {
if peer.Key == src.Key {
continue
}
peer.TrySend(MempoolCh, msg)
peer.TrySend(MempoolChannel, msg)
}
default:
@ -106,7 +106,7 @@ func (memR *MempoolReactor) BroadcastTx(tx types.Tx) error {
return err
}
msg := &TxMessage{Tx: tx}
memR.sw.Broadcast(MempoolCh, msg)
memR.sw.Broadcast(MempoolChannel, msg)
return nil
}

View File

@ -381,7 +381,7 @@ out:
for {
select {
case <-dumpAddressTicker.C:
log.Debug("Saving book to file", "size", a.Size())
log.Debug("Saving AddrBook to file", "size", a.Size())
a.saveToFile(a.filePath)
case <-a.quit:
break out

View File

@ -50,8 +50,9 @@ There are two methods for sending messages:
func (m MConnection) TrySend(chId byte, msg interface{}) bool {}
`Send(chId, msg)` is a blocking call that waits until `msg` is successfully queued
for the channel with the given id byte `chId`. The message `msg` is serialized
using the `tendermint/binary` submodule's `WriteBinary()` reflection routine.
for the channel with the given id byte `chId`, or until the request times out.
The message `msg` is serialized using the `tendermint/binary` submodule's
`WriteBinary()` reflection routine.
`TrySend(chId, msg)` is a nonblocking call that returns false if the channel's
queue is full.
@ -416,6 +417,7 @@ FOR_LOOP:
}
msgBytes := channel.recvMsgPacket(pkt)
if msgBytes != nil {
log.Debug("Received bytes", "chId", pkt.ChannelId, "msgBytes", msgBytes)
c.onReceive(pkt.ChannelId, msgBytes)
}
default:
@ -439,6 +441,17 @@ FOR_LOOP:
type ChannelDescriptor struct {
Id byte
Priority uint
SendQueueCapacity uint
RecvBufferCapacity uint
}
func (chDesc *ChannelDescriptor) FillDefaults() {
if chDesc.SendQueueCapacity == 0 {
chDesc.SendQueueCapacity = defaultSendQueueCapacity
}
if chDesc.RecvBufferCapacity == 0 {
chDesc.RecvBufferCapacity = defaultRecvBufferCapacity
}
}
// TODO: lowercase.
@ -448,7 +461,7 @@ type Channel struct {
desc *ChannelDescriptor
id byte
sendQueue chan []byte
sendQueueSize uint32
sendQueueSize uint32 // atomic.
recving []byte
sending []byte
priority uint
@ -456,6 +469,7 @@ type Channel struct {
}
func newChannel(conn *MConnection, desc *ChannelDescriptor) *Channel {
desc.FillDefaults()
if desc.Priority <= 0 {
panic("Channel default priority must be a postive integer")
}
@ -463,8 +477,8 @@ func newChannel(conn *MConnection, desc *ChannelDescriptor) *Channel {
conn: conn,
desc: desc,
id: desc.Id,
sendQueue: make(chan []byte, defaultSendQueueCapacity),
recving: make([]byte, 0, defaultRecvBufferCapacity),
sendQueue: make(chan []byte, desc.SendQueueCapacity),
recving: make([]byte, 0, desc.RecvBufferCapacity),
priority: desc.Priority,
}
}

View File

@ -13,8 +13,7 @@ import (
type Peer struct {
outbound bool
mconn *MConnection
started uint32
stopped uint32
running uint32
Key string
Data *CMap // User data.
@ -37,7 +36,7 @@ func newPeer(conn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDesc
p = &Peer{
outbound: outbound,
mconn: mconn,
stopped: 0,
running: 0,
Key: mconn.RemoteAddress.String(),
Data: NewCMap(),
}
@ -45,21 +44,21 @@ func newPeer(conn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDesc
}
func (p *Peer) start() {
if atomic.CompareAndSwapUint32(&p.started, 0, 1) {
if atomic.CompareAndSwapUint32(&p.running, 0, 1) {
log.Debug("Starting Peer", "peer", p)
p.mconn.Start()
}
}
func (p *Peer) stop() {
if atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
if atomic.CompareAndSwapUint32(&p.running, 1, 0) {
log.Debug("Stopping Peer", "peer", p)
p.mconn.Stop()
}
}
func (p *Peer) IsStopped() bool {
return atomic.LoadUint32(&p.stopped) == 1
func (p *Peer) IsRunning() bool {
return atomic.LoadUint32(&p.running) == 1
}
func (p *Peer) Connection() *MConnection {
@ -71,21 +70,21 @@ func (p *Peer) IsOutbound() bool {
}
func (p *Peer) Send(chId byte, msg interface{}) bool {
if atomic.LoadUint32(&p.stopped) == 1 {
if atomic.LoadUint32(&p.running) == 0 {
return false
}
return p.mconn.Send(chId, msg)
}
func (p *Peer) TrySend(chId byte, msg interface{}) bool {
if atomic.LoadUint32(&p.stopped) == 1 {
if atomic.LoadUint32(&p.running) == 0 {
return false
}
return p.mconn.TrySend(chId, msg)
}
func (p *Peer) CanSend(chId byte) bool {
if atomic.LoadUint32(&p.stopped) == 1 {
if atomic.LoadUint32(&p.running) == 0 {
return false
}
return p.mconn.CanSend(chId)

View File

@ -7,6 +7,7 @@ import (
// IPeerSet has a (immutable) subset of the methods of PeerSet.
type IPeerSet interface {
Has(key string) bool
Get(key string) *Peer
List() []*Peer
Size() int
}
@ -55,6 +56,17 @@ func (ps *PeerSet) Has(peerKey string) bool {
return ok
}
func (ps *PeerSet) Get(peerKey string) *Peer {
ps.mtx.Lock()
defer ps.mtx.Unlock()
item, ok := ps.lookup[peerKey]
if ok {
return item.peer
} else {
return nil
}
}
func (ps *PeerSet) Remove(peer *Peer) {
ps.mtx.Lock()
defer ps.mtx.Unlock()

View File

@ -14,7 +14,7 @@ import (
var pexErrInvalidMessage = errors.New("Invalid PEX message")
const (
PexCh = byte(0x00)
PexChannel = byte(0x00)
ensurePeersPeriodSeconds = 30
minNumOutboundPeers = 10
maxNumPeers = 50
@ -62,8 +62,9 @@ func (pexR *PEXReactor) Stop() {
func (pexR *PEXReactor) GetChannels() []*ChannelDescriptor {
return []*ChannelDescriptor{
&ChannelDescriptor{
Id: PexCh,
Id: PexChannel,
Priority: 1,
SendQueueCapacity: 10,
},
}
}
@ -97,9 +98,9 @@ func (pexR *PEXReactor) Receive(chId byte, src *Peer, msgBytes []byte) {
switch msg.(type) {
case *pexHandshakeMessage:
chainId := msg.(*pexHandshakeMessage).ChainId
if chainId != pexR.sw.chainId {
err := fmt.Sprintf("Peer is on a different chain/network. Got %s, expected %s", chainId, pexR.sw.chainId)
network := msg.(*pexHandshakeMessage).Network
if network != pexR.sw.network {
err := fmt.Sprintf("Peer is on a different chain/network. Got %s, expected %s", network, pexR.sw.network)
pexR.sw.StopPeerForError(src, err)
}
case *pexRequestMessage:
@ -122,11 +123,11 @@ func (pexR *PEXReactor) Receive(chId byte, src *Peer, msgBytes []byte) {
// Asks peer for more addresses.
func (pexR *PEXReactor) RequestPEX(peer *Peer) {
peer.Send(PexCh, &pexRequestMessage{})
peer.Send(PexChannel, &pexRequestMessage{})
}
func (pexR *PEXReactor) SendAddrs(peer *Peer, addrs []*NetAddress) {
peer.Send(PexCh, &pexAddrsMessage{Addrs: addrs})
peer.Send(PexChannel, &pexAddrsMessage{Addrs: addrs})
}
// Ensures that sufficient peers are connected. (continuous)
@ -175,10 +176,12 @@ func (pexR *PEXReactor) ensurePeers() {
alreadyDialing := pexR.sw.IsDialing(try)
alreadyConnected := pexR.sw.Peers().Has(try.String())
if alreadySelected || alreadyDialing || alreadyConnected {
/*
log.Debug("Cannot dial address", "addr", try,
"alreadySelected", alreadySelected,
"alreadyDialing", alreadyDialing,
"alreadyConnected", alreadyConnected)
*/
continue
} else {
log.Debug("Will dial address", "addr", try)
@ -237,7 +240,7 @@ func DecodeMessage(bz []byte) (msg interface{}, err error) {
A pexHandshakeMessage contains the peer's chainId
*/
type pexHandshakeMessage struct {
ChainId string
Network string
}
func (m *pexHandshakeMessage) TypeByte() byte { return msgTypeHandshake }

View File

@ -1,11 +1,9 @@
package p2p
import (
"encoding/hex"
"errors"
"fmt"
"net"
"sync/atomic"
"time"
. "github.com/tendermint/tendermint/common"
@ -29,20 +27,16 @@ or more `Channels`. So while sending outgoing messages is typically performed o
incoming messages are received on the reactor.
*/
type Switch struct {
reactors []Reactor
network string
reactors map[string]Reactor
chDescs []*ChannelDescriptor
reactorsByCh map[byte]Reactor
peers *PeerSet
dialing *CMap
listeners *CMap // listenerName -> chan interface{}
quit chan struct{}
started uint32
stopped uint32
chainId string
}
var (
ErrSwitchStopped = errors.New("Switch already stopped")
ErrSwitchDuplicatePeer = errors.New("Duplicate peer")
)
@ -50,71 +44,83 @@ const (
peerDialTimeoutSeconds = 3
)
func NewSwitch(reactors []Reactor) *Switch {
// Validate the reactors. no two reactors can share the same channel.
chDescs := []*ChannelDescriptor{}
reactorsByCh := make(map[byte]Reactor)
for _, reactor := range reactors {
reactorChannels := reactor.GetChannels()
for _, chDesc := range reactorChannels {
chId := chDesc.Id
if reactorsByCh[chId] != nil {
panic(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chId, reactorsByCh[chId], reactor))
}
chDescs = append(chDescs, chDesc)
reactorsByCh[chId] = reactor
}
}
func NewSwitch() *Switch {
sw := &Switch{
reactors: reactors,
chDescs: chDescs,
reactorsByCh: reactorsByCh,
network: "",
reactors: make(map[string]Reactor),
chDescs: make([]*ChannelDescriptor, 0),
reactorsByCh: make(map[byte]Reactor),
peers: NewPeerSet(),
dialing: NewCMap(),
listeners: NewCMap(),
quit: make(chan struct{}),
stopped: 0,
}
return sw
}
func (sw *Switch) Start() {
if atomic.CompareAndSwapUint32(&sw.started, 0, 1) {
log.Info("Starting Switch")
// Not goroutine safe.
func (sw *Switch) SetNetwork(network string) {
sw.network = network
}
// Not goroutine safe.
func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor {
// Validate the reactor.
// No two reactors can share the same channel.
reactorChannels := reactor.GetChannels()
for _, chDesc := range reactorChannels {
chId := chDesc.Id
if sw.reactorsByCh[chId] != nil {
panic(fmt.Sprintf("Channel %X has multiple reactors %v & %v", chId, sw.reactorsByCh[chId], reactor))
}
sw.chDescs = append(sw.chDescs, chDesc)
sw.reactorsByCh[chId] = reactor
}
sw.reactors[name] = reactor
return reactor
}
func (sw *Switch) Reactor(name string) Reactor {
return sw.reactors[name]
}
// Convenience function
func (sw *Switch) StartReactors() {
for _, reactor := range sw.reactors {
reactor.Start(sw)
}
}
// Convenience function
func (sw *Switch) StopReactors() {
// Stop all reactors.
for _, reactor := range sw.reactors {
reactor.Stop()
}
}
func (sw *Switch) Stop() {
if atomic.CompareAndSwapUint32(&sw.stopped, 0, 1) {
log.Info("Stopping Switch")
close(sw.quit)
// Convenience function
func (sw *Switch) StopPeers() {
// Stop each peer.
for _, peer := range sw.peers.List() {
peer.stop()
}
sw.peers = NewPeerSet()
// Stop all reactors.
for _, reactor := range sw.reactors {
reactor.Stop()
}
}
}
func (sw *Switch) Reactors() []Reactor {
// Convenience function
func (sw *Switch) Stop() {
sw.StopPeers()
sw.StopReactors()
}
// Not goroutine safe to modify.
func (sw *Switch) Reactors() map[string]Reactor {
return sw.reactors
}
func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) (*Peer, error) {
if atomic.LoadUint32(&sw.stopped) == 1 {
return nil, ErrSwitchStopped
}
peer := newPeer(conn, outbound, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError)
// Add the peer to .peers
@ -126,23 +132,19 @@ func (sw *Switch) AddPeerWithConnection(conn net.Conn, outbound bool) (*Peer, er
}
// Start the peer
go peer.start()
peer.start()
// Notify listeners.
sw.doAddPeer(peer)
// Send handshake
msg := &pexHandshakeMessage{ChainId: sw.chainId}
peer.Send(PexCh, msg)
msg := &pexHandshakeMessage{Network: sw.network}
peer.Send(PexChannel, msg)
return peer, nil
}
func (sw *Switch) DialPeerWithAddress(addr *NetAddress) (*Peer, error) {
if atomic.LoadUint32(&sw.stopped) == 1 {
return nil, ErrSwitchStopped
}
log.Debug("Dialing address", "address", addr)
sw.dialing.Set(addr.String(), addr)
conn, err := addr.DialTimeout(peerDialTimeoutSeconds * time.Second)
@ -164,13 +166,10 @@ func (sw *Switch) IsDialing(addr *NetAddress) bool {
return sw.dialing.Has(addr.String())
}
// Broadcast runs a go routine for each attemptted send, which will block
// Broadcast runs a go routine for each attempted send, which will block
// trying to send for defaultSendTimeoutSeconds. Returns a channel
// which receives success values for each attempted send (false if times out)
func (sw *Switch) Broadcast(chId byte, msg interface{}) chan bool {
if atomic.LoadUint32(&sw.stopped) == 1 {
return nil
}
successChan := make(chan bool, len(sw.peers.List()))
log.Debug("Broadcast", "channel", chId, "msg", msg)
for _, peer := range sw.peers.List() {
@ -223,14 +222,6 @@ func (sw *Switch) StopPeerGracefully(peer *Peer) {
sw.doRemovePeer(peer, nil)
}
func (sw *Switch) GetChainId() string {
return sw.chainId
}
func (sw *Switch) SetChainId(hash []byte, network string) {
sw.chainId = hex.EncodeToString(hash) + "-" + network
}
func (sw *Switch) IsListening() bool {
return sw.listeners.Size() > 0
}

View File

@ -68,12 +68,12 @@ func (tr *TestReactor) Receive(chId byte, peer *Peer, msgBytes []byte) {
//-----------------------------------------------------------------------------
// convenience method for creating two switches connected to each other.
func makeSwitchPair(t testing.TB, reactorsGenerator func() []Reactor) (*Switch, *Switch) {
// convenience method for creating bar switches connected to each other.
func makeSwitchPair(t testing.TB, initSwitch func(*Switch) *Switch) (*Switch, *Switch) {
// Create two switches that will be interconnected.
s1 := NewSwitch(reactorsGenerator())
s2 := NewSwitch(reactorsGenerator())
// Create bar switches that will be interconnected.
s1 := initSwitch(NewSwitch())
s2 := initSwitch(NewSwitch())
// Create a listener for s1
l := NewDefaultListener("tcp", ":8001", true)
@ -104,18 +104,17 @@ func makeSwitchPair(t testing.TB, reactorsGenerator func() []Reactor) (*Switch,
}
func TestSwitches(t *testing.T) {
s1, s2 := makeSwitchPair(t, func() []Reactor {
// Make two reactors of two channels each
reactors := make([]Reactor, 2)
reactors[0] = NewTestReactor([]*ChannelDescriptor{
s1, s2 := makeSwitchPair(t, func(sw *Switch) *Switch {
// Make bar reactors of bar channels each
sw.AddReactor("foo", NewTestReactor([]*ChannelDescriptor{
&ChannelDescriptor{Id: byte(0x00), Priority: 10},
&ChannelDescriptor{Id: byte(0x01), Priority: 10},
}, true)
reactors[1] = NewTestReactor([]*ChannelDescriptor{
}, true)).Start(sw) // Start the reactor
sw.AddReactor("bar", NewTestReactor([]*ChannelDescriptor{
&ChannelDescriptor{Id: byte(0x02), Priority: 10},
&ChannelDescriptor{Id: byte(0x03), Priority: 10},
}, true)
return reactors
}, true)).Start(sw) // Start the reactor
return sw
})
defer s1.Stop()
defer s2.Stop()
@ -129,8 +128,8 @@ func TestSwitches(t *testing.T) {
}
ch0Msg := "channel zero"
ch1Msg := "channel one"
ch2Msg := "channel two"
ch1Msg := "channel foo"
ch2Msg := "channel bar"
s1.Broadcast(byte(0x00), ch0Msg)
s1.Broadcast(byte(0x01), ch1Msg)
@ -140,7 +139,7 @@ func TestSwitches(t *testing.T) {
time.Sleep(5000 * time.Millisecond)
// Check message on ch0
ch0Msgs := s2.Reactors()[0].(*TestReactor).msgsReceived[byte(0x00)]
ch0Msgs := s2.Reactor("foo").(*TestReactor).msgsReceived[byte(0x00)]
if len(ch0Msgs) != 2 {
t.Errorf("Expected to have received 1 message in ch0")
}
@ -149,7 +148,7 @@ func TestSwitches(t *testing.T) {
}
// Check message on ch1
ch1Msgs := s2.Reactors()[0].(*TestReactor).msgsReceived[byte(0x01)]
ch1Msgs := s2.Reactor("foo").(*TestReactor).msgsReceived[byte(0x01)]
if len(ch1Msgs) != 1 {
t.Errorf("Expected to have received 1 message in ch1")
}
@ -158,7 +157,7 @@ func TestSwitches(t *testing.T) {
}
// Check message on ch2
ch2Msgs := s2.Reactors()[1].(*TestReactor).msgsReceived[byte(0x02)]
ch2Msgs := s2.Reactor("bar").(*TestReactor).msgsReceived[byte(0x02)]
if len(ch2Msgs) != 1 {
t.Errorf("Expected to have received 1 message in ch2")
}
@ -172,18 +171,17 @@ func BenchmarkSwitches(b *testing.B) {
b.StopTimer()
s1, s2 := makeSwitchPair(b, func() []Reactor {
// Make two reactors of two channels each
reactors := make([]Reactor, 2)
reactors[0] = NewTestReactor([]*ChannelDescriptor{
s1, s2 := makeSwitchPair(b, func(sw *Switch) *Switch {
// Make bar reactors of bar channels each
sw.AddReactor("foo", NewTestReactor([]*ChannelDescriptor{
&ChannelDescriptor{Id: byte(0x00), Priority: 10},
&ChannelDescriptor{Id: byte(0x01), Priority: 10},
}, false)
reactors[1] = NewTestReactor([]*ChannelDescriptor{
}, false))
sw.AddReactor("bar", NewTestReactor([]*ChannelDescriptor{
&ChannelDescriptor{Id: byte(0x02), Priority: 10},
&ChannelDescriptor{Id: byte(0x03), Priority: 10},
}, false)
return reactors
}, false))
return sw
})
defer s1.Stop()
defer s2.Stop()
@ -194,7 +192,7 @@ func BenchmarkSwitches(b *testing.B) {
numSuccess, numFailure := 0, 0
// Send random message from one channel to another
// Send random message from foo channel to another
for i := 0; i < b.N; i++ {
chId := byte(i % 4)
successChan := s1.Broadcast(chId, "test data")

View File

@ -1,18 +1,18 @@
package core
import (
bc "github.com/tendermint/tendermint/blockchain"
"github.com/tendermint/tendermint/consensus"
mempl "github.com/tendermint/tendermint/mempool"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/types"
)
var blockStore *types.BlockStore
var blockStore *bc.BlockStore
var consensusState *consensus.ConsensusState
var mempoolReactor *mempl.MempoolReactor
var p2pSwitch *p2p.Switch
func SetPipeBlockStore(bs *types.BlockStore) {
func SetRPCBlockStore(bs *bc.BlockStore) {
blockStore = bs
}

221
state/block_cache.go Normal file
View File

@ -0,0 +1,221 @@
package state
import (
"bytes"
"sort"
ac "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
dbm "github.com/tendermint/tendermint/db"
"github.com/tendermint/tendermint/merkle"
)
func makeStorage(db dbm.DB, root []byte) merkle.Tree {
storage := merkle.NewIAVLTree(
binary.BasicCodec,
binary.BasicCodec,
1024,
db,
)
storage.Load(root)
return storage
}
type BlockCache struct {
db dbm.DB
backend *State
accounts map[string]accountInfo
storages map[Tuple256]storageInfo
}
func NewBlockCache(backend *State) *BlockCache {
return &BlockCache{
db: backend.DB,
backend: backend,
accounts: make(map[string]accountInfo),
storages: make(map[Tuple256]storageInfo),
}
}
func (cache *BlockCache) State() *State {
return cache.backend
}
//-------------------------------------
// BlockCache.account
func (cache *BlockCache) GetAccount(addr []byte) *ac.Account {
acc, _, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
return nil
} else if acc != nil {
return acc
} else {
acc = cache.backend.GetAccount(addr)
cache.accounts[string(addr)] = accountInfo{acc, nil, false, false}
return acc
}
}
func (cache *BlockCache) UpdateAccount(acc *ac.Account) {
addr := acc.Address
// SANITY CHECK
_, storage, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
panic("UpdateAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[string(addr)] = accountInfo{acc, storage, false, true}
}
func (cache *BlockCache) RemoveAccount(addr []byte) {
// SANITY CHECK
_, _, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
panic("RemoveAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[string(addr)] = accountInfo{nil, nil, true, false}
}
// BlockCache.account
//-------------------------------------
// BlockCache.storage
func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) {
// Check cache
info, ok := cache.storages[Tuple256{addr, key}]
if ok {
return info.value
}
// Get or load storage
acc, storage, removed, dirty := cache.accounts[string(addr.Prefix(20))].unpack()
if removed {
panic("GetStorage() on removed account")
}
if storage == nil {
storage = makeStorage(cache.db, acc.StorageRoot)
cache.accounts[string(addr.Prefix(20))] = accountInfo{acc, storage, false, dirty}
}
// Load and set cache
_, val_ := storage.Get(key.Bytes())
value = Zero256
if val_ != nil {
value = RightPadWord256(val_.([]byte))
}
cache.storages[Tuple256{addr, key}] = storageInfo{value, false}
return value
}
// NOTE: Set value to zero to removed from the trie.
func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) {
_, _, removed, _ := cache.accounts[string(addr.Prefix(20))].unpack()
if removed {
panic("SetStorage() on a removed account")
}
cache.storages[Tuple256{addr, key}] = storageInfo{value, true}
}
// BlockCache.storage
//-------------------------------------
// CONTRACT the updates are in deterministic order.
func (cache *BlockCache) Sync() {
// Determine order for storage updates
// The address comes first so it'll be grouped.
storageKeys := make([]Tuple256, 0, len(cache.storages))
for keyTuple := range cache.storages {
storageKeys = append(storageKeys, keyTuple)
}
Tuple256Slice(storageKeys).Sort()
// Update storage for all account/key.
// Later we'll iterate over all the users and save storage + update storage root.
var (
curAddr Word256
curAcc *ac.Account
curAccRemoved bool
curStorage merkle.Tree
)
for _, storageKey := range storageKeys {
addr, key := Tuple256Split(storageKey)
if addr != curAddr || curAcc == nil {
acc, storage, removed, _ := cache.accounts[string(addr.Prefix(20))].unpack()
curAddr = addr
curAcc = acc
curAccRemoved = removed
curStorage = storage
}
if curAccRemoved {
continue
}
value, dirty := cache.storages[storageKey].unpack()
if !dirty {
continue
}
if value.IsZero() {
curStorage.Remove(key.Bytes())
} else {
curStorage.Set(key.Bytes(), value.Bytes())
}
}
// Determine order for accounts
addrStrs := []string{}
for addrStr := range cache.accounts {
addrStrs = append(addrStrs, addrStr)
}
sort.Strings(addrStrs)
// Update or delete accounts.
for _, addrStr := range addrStrs {
acc, storage, removed, dirty := cache.accounts[addrStr].unpack()
if removed {
removed := cache.backend.RemoveAccount(acc.Address)
if !removed {
panic(Fmt("Could not remove account to be removed: %X", acc.Address))
}
} else {
if acc == nil {
panic(Fmt("Account should not be nil for addr: %X", acc.Address))
}
if storage != nil {
newStorageRoot := storage.Save()
if !bytes.Equal(newStorageRoot, acc.StorageRoot) {
acc.StorageRoot = newStorageRoot
dirty = true
}
}
if dirty {
cache.backend.UpdateAccount(acc)
}
}
}
}
//-----------------------------------------------------------------------------
type accountInfo struct {
account *ac.Account
storage merkle.Tree
removed bool
dirty bool
}
func (accInfo accountInfo) unpack() (*ac.Account, merkle.Tree, bool, bool) {
return accInfo.account, accInfo.storage, accInfo.removed, accInfo.dirty
}
type storageInfo struct {
value Word256
dirty bool
}
func (stjInfo storageInfo) unpack() (Word256, bool) {
return stjInfo.value, stjInfo.dirty
}

18
state/common.go Normal file
View File

@ -0,0 +1,18 @@
package state
import (
ac "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm"
)
type AccountGetter interface {
GetAccount(addr []byte) *ac.Account
}
type VMAccountState interface {
GetAccount(addr Word256) *vm.Account
UpdateAccount(acc *vm.Account)
RemoveAccount(acc *vm.Account)
CreateAccount(creator *vm.Account) *vm.Account
}

593
state/execution.go Normal file
View File

@ -0,0 +1,593 @@
package state
import (
"bytes"
"errors"
"github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/vm"
)
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling ExecBlock!
func ExecBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
err := execBlock(s, block, blockPartsHeader)
if err != nil {
return err
}
// State.Hash should match block.StateHash
stateHash := s.Hash()
if !bytes.Equal(stateHash, block.StateHash) {
return Errorf("Invalid state hash. Expected %X, got %X",
stateHash, block.StateHash)
}
return nil
}
// executes transactions of a block, does not check block.StateHash
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling execBlock!
func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
// Basic block validation.
err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
if err != nil {
return err
}
// Validate block Validation.
if block.Height == 1 {
if len(block.Validation.Commits) != 0 {
return errors.New("Block at height 1 (first block) should have no Validation commits")
}
} else {
if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() {
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
s.LastBondedValidators.Size(), len(block.Validation.Commits)))
}
var sumVotingPower uint64
s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool {
commit := block.Validation.Commits[index]
if commit.IsZero() {
return false
} else {
vote := &types.Vote{
Height: block.Height - 1,
Round: commit.Round,
Type: types.VoteTypeCommit,
BlockHash: block.LastBlockHash,
BlockParts: block.LastBlockParts,
}
if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) {
sumVotingPower += val.VotingPower
return false
} else {
log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote))
err = errors.New("Invalid validation signature")
return true
}
}
})
if err != nil {
return err
}
if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 {
return errors.New("Insufficient validation voting power")
}
}
// Update Validator.LastCommitHeight as necessary.
for i, commit := range block.Validation.Commits {
if commit.IsZero() {
continue
}
_, val := s.LastBondedValidators.GetByIndex(uint(i))
if val == nil {
panic(Fmt("Failed to fetch validator at index %v", i))
}
if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.BondedValidators.Update(val_)
if !updated {
panic("Failed to update bonded validator LastCommitHeight")
}
} else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.UnbondingValidators.Update(val_)
if !updated {
panic("Failed to update unbonding validator LastCommitHeight")
}
} else {
panic("Could not find validator")
}
}
// Remember LastBondedValidators
s.LastBondedValidators = s.BondedValidators.Copy()
// Create BlockCache to cache changes to state.
blockCache := NewBlockCache(s)
// Commit each tx
for _, tx := range block.Data.Txs {
err := ExecTx(blockCache, tx, true)
if err != nil {
return InvalidTxError{tx, err}
}
}
// Now sync the BlockCache to the backend.
blockCache.Sync()
// If any unbonding periods are over,
// reward account with bonded coins.
toRelease := []*Validator{}
s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool {
if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
toRelease = append(toRelease, val)
}
return false
})
for _, val := range toRelease {
s.releaseValidator(val)
}
// If any validators haven't signed in a while,
// unbond them, they have timed out.
toTimeout := []*Validator{}
s.BondedValidators.Iterate(func(index uint, val *Validator) bool {
lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight)
if lastActivityHeight+validatorTimeoutBlocks < block.Height {
log.Info("Validator timeout", "validator", val, "height", block.Height)
toTimeout = append(toTimeout, val)
}
return false
})
for _, val := range toTimeout {
s.unbondValidator(val)
}
// Increment validator AccumPowers
s.BondedValidators.IncrementAccum(1)
s.LastBlockHeight = block.Height
s.LastBlockHash = block.Hash()
s.LastBlockParts = blockPartsHeader
s.LastBlockTime = block.Time
return nil
}
// The accounts from the TxInputs must either already have
// account.PubKey.(type) != PubKeyNil, (it must be known),
// or it must be specified in the TxInput. If redeclared,
// the TxInput is modified and input.PubKey set to PubKeyNil.
func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) {
accounts := map[string]*account.Account{}
for _, in := range ins {
// Account shouldn't be duplicated
if _, ok := accounts[string(in.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := state.GetAccount(in.Address)
if acc == nil {
return nil, types.ErrTxInvalidAddress
}
// PubKey should be present in either "account" or "in"
if err := checkInputPubKey(acc, in); err != nil {
return nil, err
}
accounts[string(in.Address)] = acc
}
for _, out := range outs {
// Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := state.GetAccount(out.Address)
// output account may be nil (new)
if acc == nil {
acc = &account.Account{
Address: out.Address,
PubKey: account.PubKeyNil{},
Sequence: 0,
Balance: 0,
}
}
accounts[string(out.Address)] = acc
}
return accounts, nil
}
func checkInputPubKey(acc *account.Account, in *types.TxInput) error {
if _, isNil := acc.PubKey.(account.PubKeyNil); isNil {
if _, isNil := in.PubKey.(account.PubKeyNil); isNil {
return types.ErrTxUnknownPubKey
}
if !bytes.Equal(in.PubKey.Address(), acc.Address) {
return types.ErrTxInvalidPubKey
}
acc.PubKey = in.PubKey
} else {
in.PubKey = account.PubKeyNil{}
}
return nil
}
func validateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("validateInputs() expects account in accounts")
}
err = validateInput(acc, signBytes, in)
if err != nil {
return
}
// Good. Add amount to total
total += in.Amount
}
return total, nil
}
func validateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (err error) {
// Check TxInput basic
if err := in.ValidateBasic(); err != nil {
return err
}
// Check signatures
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
return types.ErrTxInvalidSignature
}
// Check sequences
if acc.Sequence+1 != in.Sequence {
return types.ErrTxInvalidSequence{
Got: uint64(in.Sequence),
Expected: uint64(acc.Sequence + 1),
}
}
// Check amount
if acc.Balance < in.Amount {
return types.ErrTxInsufficientFunds
}
return nil
}
func validateOutputs(outs []*types.TxOutput) (total uint64, err error) {
for _, out := range outs {
// Check TxOutput basic
if err := out.ValidateBasic(); err != nil {
return 0, err
}
// Good. Add amount to total
total += out.Amount
}
return total, nil
}
func adjustByInputs(accounts map[string]*account.Account, ins []*types.TxInput) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("adjustByInputs() expects account in accounts")
}
if acc.Balance < in.Amount {
panic("adjustByInputs() expects sufficient funds")
}
acc.Balance -= in.Amount
acc.Sequence += 1
}
}
func adjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutput) {
for _, out := range outs {
acc := accounts[string(out.Address)]
if acc == nil {
panic("adjustByOutputs() expects account in accounts")
}
acc.Balance += out.Amount
}
}
// If the tx is invalid, an error will be returned.
// Unlike ExecBlock(), state will not be altered.
func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
// TODO: do something with fees
fees := uint64(0)
_s := blockCache.State() // hack to access validators.
// Exec tx
switch tx := tx_.(type) {
case *types.SendTx:
accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, tx.Outputs)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
outTotal, err := validateOutputs(tx.Outputs)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
adjustByInputs(accounts, tx.Inputs)
adjustByOutputs(accounts, tx.Outputs)
for _, acc := range accounts {
blockCache.UpdateAccount(acc)
}
return nil
case *types.CallTx:
var inAcc, outAcc *account.Account
// Validate input
inAcc = blockCache.GetAccount(tx.Input.Address)
if inAcc == nil {
log.Debug(Fmt("Can't find in account %X", tx.Input.Address))
return types.ErrTxInvalidAddress
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address))
return err
}
signBytes := account.SignBytes(tx)
err := validateInput(inAcc, signBytes, tx.Input)
if err != nil {
log.Debug(Fmt("validateInput failed on %X:", tx.Input.Address))
return err
}
if tx.Input.Amount < tx.Fee {
log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return types.ErrTxInsufficientFunds
}
createAccount := len(tx.Address) == 0
if !createAccount {
// Validate output
if len(tx.Address) != 20 {
log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address))
return types.ErrTxInvalidAddress
}
// this may be nil if we are still in mempool and contract was created in same block as this tx
// but that's fine, because the account will be created properly when the create tx runs in the block
// and then this won't return nil. otherwise, we take their fee
outAcc = blockCache.GetAccount(tx.Address)
}
log.Debug(Fmt("Out account: %v", outAcc))
// Good!
value := tx.Input.Amount - tx.Fee
inAcc.Sequence += 1
if runCall {
var (
gas uint64 = tx.GasLimit
err error = nil
caller *vm.Account = toVMAccount(inAcc)
callee *vm.Account = nil
code []byte = nil
txCache = NewTxCache(blockCache)
params = vm.Params{
BlockHeight: uint64(_s.LastBlockHeight),
BlockHash: RightPadWord256(_s.LastBlockHash),
BlockTime: _s.LastBlockTime.Unix(),
GasLimit: 10000000,
}
)
// Maybe create a new callee account if
// this transaction is creating a new contract.
if !createAccount {
if outAcc == nil {
// take fees (sorry pal)
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
return types.ErrTxInvalidAddress
}
callee = toVMAccount(outAcc)
code = callee.Code
log.Debug(Fmt("Calling contract %X with code %X", callee.Address, callee.Code))
} else {
callee = txCache.CreateAccount(caller)
log.Debug(Fmt("Created new account %X", callee.Address))
code = tx.Data
}
log.Debug(Fmt("Code for this contract: %X", code))
txCache.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe.
txCache.UpdateAccount(callee) // because we adjusted by input above.
vmach := vm.NewVM(txCache, params, caller.Address)
// NOTE: Call() transfers the value from caller to callee iff call succeeds.
ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas)
if err != nil {
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
log.Debug(Fmt("Error on execution: %v", err))
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
// Throw away 'txCache' which holds incomplete updates (don't sync it).
} else {
log.Debug("Successful execution")
// Success
if createAccount {
callee.Code = ret
}
txCache.Sync()
}
// Create a receipt from the ret and whether errored.
log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
} else {
// The mempool does not call txs until
// the proposer determines the order of txs.
// So mempool will skip the actual .Call(),
// and only deduct from the caller's balance.
inAcc.Balance -= value
if createAccount {
inAcc.Sequence += 1
}
blockCache.UpdateAccount(inAcc)
}
return nil
case *types.BondTx:
valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address())
if valInfo != nil {
// TODO: In the future, check that the validator wasn't destroyed,
// add funds, merge UnbondTo outputs, and unbond validator.
return errors.New("Adding coins to existing validators not yet supported")
}
accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, nil)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
if err := tx.PubKey.ValidateBasic(); err != nil {
return err
}
outTotal, err := validateOutputs(tx.UnbondTo)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
adjustByInputs(accounts, tx.Inputs)
for _, acc := range accounts {
blockCache.UpdateAccount(acc)
}
// Add ValidatorInfo
_s.SetValidatorInfo(&ValidatorInfo{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
UnbondTo: tx.UnbondTo,
FirstBondHeight: _s.LastBlockHeight + 1,
FirstBondAmount: outTotal,
})
// Add Validator
added := _s.BondedValidators.Add(&Validator{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
BondHeight: _s.LastBlockHeight + 1,
VotingPower: outTotal,
Accum: 0,
})
if !added {
panic("Failed to add validator")
}
return nil
case *types.UnbondTx:
// The validator must be active
_, val := _s.BondedValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be greater than val.LastCommitHeight
if tx.Height <= val.LastCommitHeight {
return errors.New("Invalid unbond height")
}
// Good!
_s.unbondValidator(val)
return nil
case *types.RebondTx:
// The validator must be inactive
_, val := _s.UnbondingValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be equal to the next height
if tx.Height != _s.LastBlockHeight+1 {
return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", _s.LastBlockHeight+1, tx.Height))
}
// Good!
_s.rebondValidator(val)
return nil
case *types.DupeoutTx:
// Verify the signatures
_, accused := _s.BondedValidators.GetByAddress(tx.Address)
if accused == nil {
_, accused = _s.UnbondingValidators.GetByAddress(tx.Address)
if accused == nil {
return types.ErrTxInvalidAddress
}
}
voteASignBytes := account.SignBytes(&tx.VoteA)
voteBSignBytes := account.SignBytes(&tx.VoteB)
if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
!accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
return types.ErrTxInvalidSignature
}
// Verify equivocation
// 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 tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round {
// Check special case (not an error, validator must be slashed!)
// Validators should not sign another vote after committing.
} else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round {
// We need to check both orderings of the votes
} else {
if tx.VoteA.Round != tx.VoteB.Round {
return errors.New("DupeoutTx rounds don't match")
}
if tx.VoteA.Type != tx.VoteB.Type {
return errors.New("DupeoutTx types don't match")
}
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
return errors.New("DupeoutTx blockhashes shouldn't match")
}
}
// Good! (Bad validator!)
_s.destroyValidator(accused)
return nil
default:
panic("Unknown Tx type")
}
}

View File

@ -2,17 +2,14 @@ package state
import (
"bytes"
"errors"
"fmt"
"time"
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
dbm "github.com/tendermint/tendermint/db"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/vm"
)
var (
@ -25,17 +22,6 @@ var (
//-----------------------------------------------------------------------------
type InvalidTxError struct {
Tx types.Tx
Reason error
}
func (txErr InvalidTxError) Error() string {
return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
}
//-----------------------------------------------------------------------------
// NOTE: not goroutine-safe.
type State struct {
DB dbm.DB
@ -78,7 +64,6 @@ func LoadState(db dbm.DB) *State {
return s
}
// Save this state into the db.
func (s *State) Save() {
s.accounts.Save()
s.validatorInfos.Save()
@ -98,6 +83,9 @@ func (s *State) Save() {
s.DB.Set(stateKey, buf.Bytes())
}
// CONTRACT:
// Copy() is a cheap way to take a snapshot,
// as if State were copied by value.
func (s *State) Copy() *State {
return &State{
DB: s.DB,
@ -113,437 +101,81 @@ func (s *State) Copy() *State {
}
}
// The accounts from the TxInputs must either already have
// account.PubKey.(type) != PubKeyNil, (it must be known),
// or it must be specified in the TxInput. If redeclared,
// the TxInput is modified and input.PubKey set to PubKeyNil.
func (s *State) GetOrMakeAccounts(ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) {
accounts := map[string]*account.Account{}
for _, in := range ins {
// Account shouldn't be duplicated
if _, ok := accounts[string(in.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
// Returns a hash that represents the state data, excluding Last*
func (s *State) Hash() []byte {
hashables := []merkle.Hashable{
s.BondedValidators,
s.UnbondingValidators,
s.accounts,
s.validatorInfos,
}
acc := s.GetAccount(in.Address)
return merkle.HashFromHashables(hashables)
}
// Mutates the block in place and updates it with new state hash.
func (s *State) SetBlockStateHash(block *types.Block) error {
sCopy := s.Copy()
err := execBlock(sCopy, block, types.PartSetHeader{})
if err != nil {
return err
}
// Set block.StateHash
block.StateHash = sCopy.Hash()
return nil
}
//-------------------------------------
// State.accounts
// The returned Account is a copy, so mutating it
// has no side effects.
// Implements Statelike
func (s *State) GetAccount(address []byte) *account.Account {
_, acc := s.accounts.Get(address)
if acc == nil {
return nil, types.ErrTxInvalidAddress
return nil
}
// PubKey should be present in either "account" or "in"
if err := checkInputPubKey(acc, in); err != nil {
return nil, err
}
accounts[string(in.Address)] = acc
}
for _, out := range outs {
// Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := s.GetAccount(out.Address)
// output account may be nil (new)
if acc == nil {
acc = &account.Account{
Address: out.Address,
PubKey: account.PubKeyNil{},
Sequence: 0,
Balance: 0,
}
}
accounts[string(out.Address)] = acc
}
return accounts, nil
return acc.(*account.Account).Copy()
}
func checkInputPubKey(acc *account.Account, in *types.TxInput) error {
if _, isNil := acc.PubKey.(account.PubKeyNil); isNil {
if _, isNil := in.PubKey.(account.PubKeyNil); isNil {
return types.ErrTxUnknownPubKey
}
if !bytes.Equal(in.PubKey.Address(), acc.Address) {
return types.ErrTxInvalidPubKey
}
acc.PubKey = in.PubKey
} else {
in.PubKey = account.PubKeyNil{}
}
return nil
// The account is copied before setting, so mutating it
// afterwards has no side effects.
// Implements Statelike
func (s *State) UpdateAccount(account *account.Account) bool {
return s.accounts.Set(account.Address, account.Copy())
}
func (s *State) ValidateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("ValidateInputs() expects account in accounts")
}
err = s.ValidateInput(acc, signBytes, in)
if err != nil {
return
}
// Good. Add amount to total
total += in.Amount
}
return total, nil
// Implements Statelike
func (s *State) RemoveAccount(address []byte) bool {
_, removed := s.accounts.Remove(address)
return removed
}
func (s *State) ValidateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (err error) {
// Check TxInput basic
if err := in.ValidateBasic(); err != nil {
return err
}
// Check signatures
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
return types.ErrTxInvalidSignature
}
// Check sequences
if acc.Sequence+1 != in.Sequence {
return types.ErrTxInvalidSequence{
Got: uint64(in.Sequence),
Expected: uint64(acc.Sequence + 1),
}
}
// Check amount
if acc.Balance < in.Amount {
return types.ErrTxInsufficientFunds
}
return nil
// The returned Account is a copy, so mutating it
// has no side effects.
func (s *State) GetAccounts() merkle.Tree {
return s.accounts.Copy()
}
func (s *State) ValidateOutputs(outs []*types.TxOutput) (total uint64, err error) {
for _, out := range outs {
// Check TxOutput basic
if err := out.ValidateBasic(); err != nil {
return 0, err
// State.accounts
//-------------------------------------
// State.validators
// 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
}
// Good. Add amount to total
total += out.Amount
}
return total, nil
return valInfo.(*ValidatorInfo).Copy()
}
func (s *State) AdjustByInputs(accounts map[string]*account.Account, ins []*types.TxInput) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("AdjustByInputs() expects account in accounts")
}
if acc.Balance < in.Amount {
panic("AdjustByInputs() expects sufficient funds")
}
acc.Balance -= in.Amount
acc.Sequence += 1
}
}
func (s *State) AdjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutput) {
for _, out := range outs {
acc := accounts[string(out.Address)]
if acc == nil {
panic("AdjustByOutputs() expects account in accounts")
}
acc.Balance += out.Amount
}
}
// If the tx is invalid, an error will be returned.
// Unlike AppendBlock(), state will not be altered.
func (s *State) ExecTx(tx_ types.Tx, runCall bool) error {
// TODO: do something with fees
fees := uint64(0)
// Exec tx
switch tx := tx_.(type) {
case *types.SendTx:
accounts, err := s.GetOrMakeAccounts(tx.Inputs, tx.Outputs)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
outTotal, err := s.ValidateOutputs(tx.Outputs)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
s.AdjustByInputs(accounts, tx.Inputs)
s.AdjustByOutputs(accounts, tx.Outputs)
s.UpdateAccounts(accounts)
return nil
case *types.CallTx:
var inAcc, outAcc *account.Account
// Validate input
inAcc = s.GetAccount(tx.Input.Address)
if inAcc == nil {
log.Debug(Fmt("Can't find in account %X", tx.Input.Address))
return types.ErrTxInvalidAddress
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address))
return err
}
signBytes := account.SignBytes(tx)
err := s.ValidateInput(inAcc, signBytes, tx.Input)
if err != nil {
log.Debug(Fmt("ValidateInput failed on %X:", tx.Input.Address))
return err
}
if tx.Input.Amount < tx.Fee {
log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return types.ErrTxInsufficientFunds
}
createAccount := len(tx.Address) == 0
if !createAccount {
// Validate output
if len(tx.Address) != 20 {
log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address))
return types.ErrTxInvalidAddress
}
// this may be nil if we are still in mempool and contract was created in same block as this tx
// but that's fine, because the account will be created properly when the create tx runs in the block
// and then this won't return nil. otherwise, we take their fee
outAcc = s.GetAccount(tx.Address)
}
log.Debug(Fmt("Out account: %v", outAcc))
// Good!
value := tx.Input.Amount - tx.Fee
inAcc.Sequence += 1
if runCall {
var (
gas uint64 = tx.GasLimit
err error = nil
caller *vm.Account = toVMAccount(inAcc)
callee *vm.Account = nil
code []byte = nil
appState = NewVMAppState(s) // TODO: confusing.
params = vm.Params{
BlockHeight: uint64(s.LastBlockHeight),
BlockHash: vm.BytesToWord(s.LastBlockHash),
BlockTime: s.LastBlockTime.Unix(),
GasLimit: 10000000,
}
)
// Maybe create a new callee account if
// this transaction is creating a new contract.
if !createAccount {
if outAcc == nil {
// take fees (sorry pal)
inAcc.Balance -= tx.Fee
s.UpdateAccount(inAcc)
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
return types.ErrTxInvalidAddress
}
callee = toVMAccount(outAcc)
code = callee.Code
log.Debug(Fmt("Calling contract %X with code %X", callee.Address.Address(), callee.Code))
} else {
callee, err = appState.CreateAccount(caller)
if err != nil {
log.Debug(Fmt("Error creating account"))
return err
}
log.Debug(Fmt("Created new account %X", callee.Address.Address()))
code = tx.Data
}
log.Debug(Fmt("Code for this contract: %X", code))
appState.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe.
appState.UpdateAccount(callee) // because we adjusted by input above.
vmach := vm.NewVM(appState, params, caller.Address)
// NOTE: Call() transfers the value from caller to callee iff call succeeds.
ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas)
if err != nil {
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
log.Debug(Fmt("Error on execution: %v", err))
inAcc.Balance -= tx.Fee
s.UpdateAccount(inAcc)
// Throw away 'appState' which holds incomplete updates (don't sync it).
} else {
log.Debug("Successful execution")
// Success
if createAccount {
callee.Code = ret
}
appState.Sync()
}
// Create a receipt from the ret and whether errored.
log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
} else {
// The mempool does not call txs until
// the proposer determines the order of txs.
// So mempool will skip the actual .Call(),
// and only deduct from the caller's balance.
inAcc.Balance -= value
if createAccount {
inAcc.Sequence += 1
}
s.UpdateAccount(inAcc)
}
return nil
case *types.BondTx:
valInfo := s.GetValidatorInfo(tx.PubKey.Address())
if valInfo != nil {
// TODO: In the future, check that the validator wasn't destroyed,
// add funds, merge UnbondTo outputs, and unbond validator.
return errors.New("Adding coins to existing validators not yet supported")
}
accounts, err := s.GetOrMakeAccounts(tx.Inputs, nil)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
if err := tx.PubKey.ValidateBasic(); err != nil {
return err
}
outTotal, err := s.ValidateOutputs(tx.UnbondTo)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
s.AdjustByInputs(accounts, tx.Inputs)
s.UpdateAccounts(accounts)
// Add ValidatorInfo
s.SetValidatorInfo(&ValidatorInfo{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
UnbondTo: tx.UnbondTo,
FirstBondHeight: s.LastBlockHeight + 1,
FirstBondAmount: outTotal,
})
// Add Validator
added := s.BondedValidators.Add(&Validator{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
BondHeight: s.LastBlockHeight + 1,
VotingPower: outTotal,
Accum: 0,
})
if !added {
panic("Failed to add validator")
}
return nil
case *types.UnbondTx:
// The validator must be active
_, val := s.BondedValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be greater than val.LastCommitHeight
if tx.Height <= val.LastCommitHeight {
return errors.New("Invalid unbond height")
}
// Good!
s.unbondValidator(val)
return nil
case *types.RebondTx:
// The validator must be inactive
_, val := s.UnbondingValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be equal to the next height
if tx.Height != s.LastBlockHeight+1 {
return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", s.LastBlockHeight+1, tx.Height))
}
// Good!
s.rebondValidator(val)
return nil
case *types.DupeoutTx:
// Verify the signatures
_, accused := s.BondedValidators.GetByAddress(tx.Address)
if accused == nil {
_, accused = s.UnbondingValidators.GetByAddress(tx.Address)
if accused == nil {
return types.ErrTxInvalidAddress
}
}
voteASignBytes := account.SignBytes(&tx.VoteA)
voteBSignBytes := account.SignBytes(&tx.VoteB)
if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
!accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
return types.ErrTxInvalidSignature
}
// Verify equivocation
// 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 tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round {
// Check special case (not an error, validator must be slashed!)
// Validators should not sign another vote after committing.
} else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round {
// We need to check both orderings of the votes
} else {
if tx.VoteA.Round != tx.VoteB.Round {
return errors.New("DupeoutTx rounds don't match")
}
if tx.VoteA.Type != tx.VoteB.Type {
return errors.New("DupeoutTx types don't match")
}
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
return errors.New("DupeoutTx blockhashes shouldn't match")
}
}
// Good! (Bad validator!)
s.destroyValidator(accused)
return nil
default:
panic("Unknown Tx type")
}
// Returns false if new, true if updated.
// The valInfo is copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) {
return s.validatorInfos.Set(valInfo.Address, valInfo.Copy())
}
func (s *State) unbondValidator(val *Validator) {
@ -582,12 +214,14 @@ func (s *State) releaseValidator(val *Validator) {
s.SetValidatorInfo(valInfo)
// Send coins back to UnbondTo outputs
accounts, err := s.GetOrMakeAccounts(nil, valInfo.UnbondTo)
accounts, err := getOrMakeAccounts(s, nil, valInfo.UnbondTo)
if err != nil {
panic("Couldn't get or make unbondTo accounts")
}
s.AdjustByOutputs(accounts, valInfo.UnbondTo)
s.UpdateAccounts(accounts)
adjustByOutputs(accounts, valInfo.UnbondTo)
for _, acc := range accounts {
s.UpdateAccount(acc)
}
// Remove validator from UnbondingValidators
_, removed := s.UnbondingValidators.Remove(val.Address)
@ -617,219 +251,26 @@ func (s *State) destroyValidator(val *Validator) {
}
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling AppendBlock!
func (s *State) AppendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error {
err := s.appendBlock(block, blockPartsHeader)
if err != nil {
return err
}
// State.Hash should match block.StateHash
stateHash := s.Hash()
if !bytes.Equal(stateHash, block.StateHash) {
return Errorf("Invalid state hash. Expected %X, got %X",
stateHash, block.StateHash)
}
return nil
// State.validators
//-------------------------------------
// State.storage
func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) {
storage = merkle.NewIAVLTree(binary.BasicCodec, binary.BasicCodec, 1024, s.DB)
storage.Load(hash)
return storage
}
func (s *State) SetBlockStateHash(block *types.Block) error {
sCopy := s.Copy()
err := sCopy.appendBlock(block, types.PartSetHeader{})
if err != nil {
return err
}
// Set block.StateHash
block.StateHash = sCopy.Hash()
return nil
// State.storage
//-------------------------------------
//-----------------------------------------------------------------------------
type InvalidTxError struct {
Tx types.Tx
Reason error
}
// Appends the block, does not check block.StateHash
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling appendBlock!
func (s *State) appendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error {
// Basic block validation.
err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
if err != nil {
return err
}
// Validate block Validation.
if block.Height == 1 {
if len(block.Validation.Commits) != 0 {
return errors.New("Block at height 1 (first block) should have no Validation commits")
}
} else {
if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() {
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
s.LastBondedValidators.Size(), len(block.Validation.Commits)))
}
var sumVotingPower uint64
s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool {
commit := block.Validation.Commits[index]
if commit.IsZero() {
return false
} else {
vote := &types.Vote{
Height: block.Height - 1,
Round: commit.Round,
Type: types.VoteTypeCommit,
BlockHash: block.LastBlockHash,
BlockParts: block.LastBlockParts,
}
if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) {
sumVotingPower += val.VotingPower
return false
} else {
log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote))
err = errors.New("Invalid validation signature")
return true
}
}
})
if err != nil {
return err
}
if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 {
return errors.New("Insufficient validation voting power")
}
}
// Update Validator.LastCommitHeight as necessary.
for i, commit := range block.Validation.Commits {
if commit.IsZero() {
continue
}
_, val := s.LastBondedValidators.GetByIndex(uint(i))
if val == nil {
panic(Fmt("Failed to fetch validator at index %v", i))
}
if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.BondedValidators.Update(val_)
if !updated {
panic("Failed to update bonded validator LastCommitHeight")
}
} else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.UnbondingValidators.Update(val_)
if !updated {
panic("Failed to update unbonding validator LastCommitHeight")
}
} else {
panic("Could not find validator")
}
}
// Remember LastBondedValidators
s.LastBondedValidators = s.BondedValidators.Copy()
// Commit each tx
for _, tx := range block.Data.Txs {
err := s.ExecTx(tx, true)
if err != nil {
return InvalidTxError{tx, err}
}
}
// If any unbonding periods are over,
// reward account with bonded coins.
toRelease := []*Validator{}
s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool {
if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
toRelease = append(toRelease, val)
}
return false
})
for _, val := range toRelease {
s.releaseValidator(val)
}
// If any validators haven't signed in a while,
// unbond them, they have timed out.
toTimeout := []*Validator{}
s.BondedValidators.Iterate(func(index uint, val *Validator) bool {
lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight)
if lastActivityHeight+validatorTimeoutBlocks < block.Height {
log.Info("Validator timeout", "validator", val, "height", block.Height)
toTimeout = append(toTimeout, val)
}
return false
})
for _, val := range toTimeout {
s.unbondValidator(val)
}
// Increment validator AccumPowers
s.BondedValidators.IncrementAccum(1)
s.LastBlockHeight = block.Height
s.LastBlockHash = block.Hash()
s.LastBlockParts = blockPartsHeader
s.LastBlockTime = block.Time
return nil
}
// The returned Account is a copy, so mutating it
// has no side effects.
func (s *State) GetAccount(address []byte) *account.Account {
_, acc := s.accounts.Get(address)
if acc == nil {
return nil
}
return acc.(*account.Account).Copy()
}
// The returned Account is a copy, so mutating it
// has no side effects.
func (s *State) GetAccounts() merkle.Tree {
return s.accounts.Copy()
}
// The account is copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) UpdateAccount(account *account.Account) {
s.accounts.Set(account.Address, account.Copy())
}
// The accounts are copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) UpdateAccounts(accounts map[string]*account.Account) {
for _, acc := range accounts {
s.accounts.Set(acc.Address, acc.Copy())
}
}
func (s *State) RemoveAccount(address []byte) bool {
_, removed := s.accounts.Remove(address)
return removed
}
// 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 valInfo is copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) {
return s.validatorInfos.Set(valInfo.Address, valInfo.Copy())
}
// Returns a hash that represents the state data,
// excluding Last*
func (s *State) Hash() []byte {
hashables := []merkle.Hashable{
s.BondedValidators,
s.UnbondingValidators,
s.accounts,
s.validatorInfos,
}
return merkle.HashFromHashables(hashables)
func (txErr InvalidTxError) Error() string {
return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
}

View File

@ -2,7 +2,6 @@ package state
import (
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/types"
@ -11,6 +10,17 @@ import (
"time"
)
func execTxWithState(state *State, tx types.Tx, runCall bool) error {
cache := NewBlockCache(state)
err := ExecTx(cache, tx, runCall)
if err != nil {
return err
} else {
cache.Sync()
return nil
}
}
func TestCopyState(t *testing.T) {
// Generate a random state
s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000)
@ -91,10 +101,10 @@ func TestGenesisSaveLoad(t *testing.T) {
// Make complete block and blockParts
block := makeBlock(t, s0, nil, nil)
blockParts := types.NewPartSetFromData(binary.BinaryBytes(block))
blockParts := block.MakePartSet()
// Now append the block to s0.
err := s0.AppendBlock(block, blockParts.Header())
err := ExecBlock(s0, block, blockParts.Header())
if err != nil {
t.Error("Error appending initial block:", err)
}
@ -183,7 +193,7 @@ func TestTxSequence(t *testing.T) {
tx := makeSendTx(sequence)
tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
stateCopy := state.Copy()
err := stateCopy.ExecTx(tx, true)
err := execTxWithState(stateCopy, tx, true)
if i == 1 {
// Sequence is good.
if err != nil {
@ -242,7 +252,7 @@ func TestTxs(t *testing.T) {
}
tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
err := state.ExecTx(tx, true)
err := execTxWithState(state, tx, true)
if err != nil {
t.Errorf("Got error in executing send transaction, %v", err)
}
@ -279,7 +289,7 @@ func TestTxs(t *testing.T) {
},
}
tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
err := state.ExecTx(tx, true)
err := execTxWithState(state, tx, true)
if err != nil {
t.Errorf("Got error in executing bond transaction, %v", err)
}
@ -338,7 +348,7 @@ func TestAddValidator(t *testing.T) {
// Make complete block and blockParts
block0 := makeBlock(t, s0, nil, []types.Tx{bondTx})
block0Parts := types.NewPartSetFromData(binary.BinaryBytes(block0))
block0Parts := block0.MakePartSet()
// Sanity check
if s0.BondedValidators.Size() != 1 {
@ -346,7 +356,7 @@ func TestAddValidator(t *testing.T) {
}
// Now append the block to s0.
err := s0.AppendBlock(block0, block0Parts.Header())
err := ExecBlock(s0, block0, block0Parts.Header())
if err != nil {
t.Error("Error appending initial block:", err)
}
@ -379,8 +389,8 @@ func TestAddValidator(t *testing.T) {
},
}, nil,
)
block1Parts := types.NewPartSetFromData(binary.BinaryBytes(block1))
err = s0.AppendBlock(block1, block1Parts.Header())
block1Parts := block1.MakePartSet()
err = ExecBlock(s0, block1, block1Parts.Header())
if err != nil {
t.Error("Error appending secondary block:", err)
}

191
state/tx_cache.go Normal file
View File

@ -0,0 +1,191 @@
package state
import (
ac "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm"
"github.com/tendermint/tendermint/vm/sha3"
)
type TxCache struct {
backend *BlockCache
accounts map[Word256]vmAccountInfo
storages map[Tuple256]Word256
logs []*vm.Log
}
func NewTxCache(backend *BlockCache) *TxCache {
return &TxCache{
backend: backend,
accounts: make(map[Word256]vmAccountInfo),
storages: make(map[Tuple256]Word256),
logs: make([]*vm.Log, 0),
}
}
//-------------------------------------
// TxCache.account
func (cache *TxCache) GetAccount(addr Word256) *vm.Account {
acc, removed := vmUnpack(cache.accounts[addr])
if removed {
return nil
} else {
return acc
}
}
func (cache *TxCache) UpdateAccount(acc *vm.Account) {
addr := acc.Address
// SANITY CHECK
_, removed := vmUnpack(cache.accounts[addr])
if removed {
panic("UpdateAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[addr] = vmAccountInfo{acc, false}
}
func (cache *TxCache) RemoveAccount(acc *vm.Account) {
addr := acc.Address
// SANITY CHECK
_, removed := vmUnpack(cache.accounts[addr])
if removed {
panic("RemoveAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[addr] = vmAccountInfo{acc, true}
}
// Creates a 20 byte address and bumps the creator's nonce.
func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account {
// Generate an address
nonce := creator.Nonce
creator.Nonce += 1
addr := RightPadWord256(NewContractAddress(creator.Address.Prefix(20), nonce))
// Create account from address.
account, removed := vmUnpack(cache.accounts[addr])
if removed || account == nil {
account = &vm.Account{
Address: addr,
Balance: 0,
Code: nil,
Nonce: 0,
StorageRoot: Zero256,
}
cache.accounts[addr] = vmAccountInfo{account, false}
return account
} else {
panic(Fmt("Could not create account, address already exists: %X", addr))
}
}
// TxCache.account
//-------------------------------------
// TxCache.storage
func (cache *TxCache) GetStorage(addr Word256, key Word256) Word256 {
// Check cache
value, ok := cache.storages[Tuple256{addr, key}]
if ok {
return value
}
// Load from backend
return cache.backend.GetStorage(addr, key)
}
// NOTE: Set value to zero to removed from the trie.
func (cache *TxCache) SetStorage(addr Word256, key Word256, value Word256) {
_, removed := vmUnpack(cache.accounts[addr])
if removed {
panic("SetStorage() on a removed account")
}
cache.storages[Tuple256{addr, key}] = value
}
// TxCache.storage
//-------------------------------------
// These updates do not have to be in deterministic order,
// the backend is responsible for ordering updates.
func (cache *TxCache) Sync() {
// Remove or update storage
for addrKey, value := range cache.storages {
addr, key := Tuple256Split(addrKey)
cache.backend.SetStorage(addr, key, value)
}
// Remove or update accounts
for addr, accInfo := range cache.accounts {
acc, removed := vmUnpack(accInfo)
if removed {
cache.backend.RemoveAccount(addr.Prefix(20))
} else {
cache.backend.UpdateAccount(toStateAccount(acc))
}
}
// TODO support logs, add them to the cache somehow.
}
func (cache *TxCache) AddLog(log *vm.Log) {
cache.logs = append(cache.logs, log)
}
//-----------------------------------------------------------------------------
// Convenience function to return address of new contract
func NewContractAddress(caller []byte, nonce uint64) []byte {
temp := make([]byte, 32+8)
copy(temp, caller)
PutUint64(temp[32:], nonce)
return sha3.Sha3(temp)[:20]
}
// Converts backend.Account to vm.Account struct.
func toVMAccount(acc *ac.Account) *vm.Account {
return &vm.Account{
Address: RightPadWord256(acc.Address),
Balance: acc.Balance,
Code: acc.Code, // This is crazy.
Nonce: uint64(acc.Sequence),
StorageRoot: RightPadWord256(acc.StorageRoot),
Other: acc.PubKey,
}
}
// Converts vm.Account to backend.Account struct.
func toStateAccount(acc *vm.Account) *ac.Account {
pubKey, ok := acc.Other.(ac.PubKey)
if !ok {
pubKey = ac.PubKeyNil{}
}
var storageRoot []byte
if acc.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = acc.StorageRoot.Bytes()
}
return &ac.Account{
Address: acc.Address.Prefix(20),
PubKey: pubKey,
Balance: acc.Balance,
Code: acc.Code,
Sequence: uint(acc.Nonce),
StorageRoot: storageRoot,
}
}
type vmAccountInfo struct {
account *vm.Account
removed bool
}
func vmUnpack(accInfo vmAccountInfo) (*vm.Account, bool) {
return accInfo.account, accInfo.removed
}

View File

@ -2,12 +2,15 @@ package state
import (
"bytes"
"errors"
"fmt"
"sort"
"strings"
"github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/types"
)
// ValidatorSet represent a set of *Validator at a given height.
@ -198,6 +201,50 @@ func (valSet *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) {
}
}
// Verify that +2/3 of the set had signed the given signBytes
func (valSet *ValidatorSet) VerifyValidation(hash []byte, parts types.PartSetHeader, height uint, v *types.Validation) error {
if valSet.Size() != uint(len(v.Commits)) {
return errors.New(Fmt("Invalid validation -- wrong set size: %v vs %v",
valSet.Size(), len(v.Commits)))
}
talliedVotingPower := uint64(0)
seenValidators := map[string]struct{}{}
for idx, commit := range v.Commits {
// may be zero, in which case skip.
if commit.Signature.IsZero() {
continue
}
_, val := valSet.GetByIndex(uint(idx))
commitSignBytes := account.SignBytes(&types.Vote{
Height: height, Round: commit.Round, Type: types.VoteTypeCommit,
BlockHash: hash,
BlockParts: parts,
})
// Validate
if _, seen := seenValidators[string(val.Address)]; seen {
return Errorf("Duplicate validator for commit %v for Validation %v", commit, v)
}
if !val.PubKey.VerifyBytes(commitSignBytes, commit.Signature) {
return Errorf("Invalid signature for commit %v for Validation %v", commit, v)
}
// Tally
seenValidators[string(val.Address)] = struct{}{}
talliedVotingPower += val.VotingPower
}
if talliedVotingPower > valSet.TotalVotingPower()*2/3 {
return nil
} else {
return Errorf("insufficient voting power %v, needed %v",
talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
}
}
func (valSet *ValidatorSet) String() string {
return valSet.StringIndented("")
}

View File

@ -1,265 +0,0 @@
package state
import (
"bytes"
"sort"
ac "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/vm"
"github.com/tendermint/tendermint/vm/sha3"
)
// Converts state.Account to vm.Account struct.
func toVMAccount(acc *ac.Account) *vm.Account {
return &vm.Account{
Address: vm.BytesToWord(acc.Address),
Balance: acc.Balance,
Code: acc.Code, // This is crazy.
Nonce: uint64(acc.Sequence),
StorageRoot: vm.BytesToWord(acc.StorageRoot),
Other: acc.PubKey,
}
}
// Converts vm.Account to state.Account struct.
func toStateAccount(acc *vm.Account) *ac.Account {
pubKey, ok := acc.Other.(ac.PubKey)
if !ok {
pubKey = ac.PubKeyNil{}
}
var storageRoot []byte
if acc.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = acc.StorageRoot.Bytes()
}
return &ac.Account{
Address: acc.Address.Address(),
PubKey: pubKey,
Balance: acc.Balance,
Code: acc.Code,
Sequence: uint(acc.Nonce),
StorageRoot: storageRoot,
}
}
//-----------------------------------------------------------------------------
type AccountInfo struct {
account *vm.Account
deleted bool
}
type VMAppState struct {
state *State
accounts map[string]AccountInfo
storage map[string]vm.Word
logs []*vm.Log
}
func NewVMAppState(state *State) *VMAppState {
return &VMAppState{
state: state,
accounts: make(map[string]AccountInfo),
storage: make(map[string]vm.Word),
logs: make([]*vm.Log, 0),
}
}
func unpack(accInfo AccountInfo) (*vm.Account, bool) {
return accInfo.account, accInfo.deleted
}
func (vas *VMAppState) GetAccount(addr vm.Word) (*vm.Account, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if deleted {
return nil, Errorf("Account was deleted: %X", addr)
} else if account != nil {
return account, nil
} else {
acc := vas.state.GetAccount(addr.Address())
if acc == nil {
return nil, Errorf("Invalid account addr: %X", addr)
}
return toVMAccount(acc), nil
}
}
func (vas *VMAppState) UpdateAccount(account *vm.Account) error {
accountInfo, ok := vas.accounts[account.Address.String()]
if !ok {
vas.accounts[account.Address.String()] = AccountInfo{account, false}
return nil
}
account, deleted := unpack(accountInfo)
if deleted {
return Errorf("Account was deleted: %X", account.Address)
} else {
vas.accounts[account.Address.String()] = AccountInfo{account, false}
return nil
}
}
func (vas *VMAppState) DeleteAccount(account *vm.Account) error {
accountInfo, ok := vas.accounts[account.Address.String()]
if !ok {
vas.accounts[account.Address.String()] = AccountInfo{account, true}
return nil
}
account, deleted := unpack(accountInfo)
if deleted {
return Errorf("Account was already deleted: %X", account.Address)
} else {
vas.accounts[account.Address.String()] = AccountInfo{account, true}
return nil
}
}
// Creates a 20 byte address and bumps the creator's nonce.
func (vas *VMAppState) CreateAccount(creator *vm.Account) (*vm.Account, error) {
// Generate an address
nonce := creator.Nonce
creator.Nonce += 1
addr := vm.RightPadWord(NewContractAddress(creator.Address.Address(), nonce))
// Create account from address.
account, deleted := unpack(vas.accounts[addr.String()])
if deleted || account == nil {
account = &vm.Account{
Address: addr,
Balance: 0,
Code: nil,
Nonce: 0,
StorageRoot: vm.Zero,
}
vas.accounts[addr.String()] = AccountInfo{account, false}
return account, nil
} else {
panic(Fmt("Could not create account, address already exists: %X", addr))
// return nil, Errorf("Account already exists: %X", addr)
}
}
func (vas *VMAppState) GetStorage(addr vm.Word, key vm.Word) (vm.Word, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if account == nil {
return vm.Zero, Errorf("Invalid account addr: %X", addr)
} else if deleted {
return vm.Zero, Errorf("Account was deleted: %X", addr)
}
value, ok := vas.storage[addr.String()+key.String()]
if ok {
return value, nil
} else {
return vm.Zero, nil
}
}
// NOTE: Set value to zero to delete from the trie.
func (vas *VMAppState) SetStorage(addr vm.Word, key vm.Word, value vm.Word) (bool, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if account == nil {
return false, Errorf("Invalid account addr: %X", addr)
} else if deleted {
return false, Errorf("Account was deleted: %X", addr)
}
_, ok := vas.storage[addr.String()+key.String()]
vas.storage[addr.String()+key.String()] = value
return ok, nil
}
// CONTRACT the updates are in deterministic order.
func (vas *VMAppState) Sync() {
// Determine order for accounts
addrStrs := []string{}
for addrStr := range vas.accounts {
addrStrs = append(addrStrs, addrStr)
}
sort.Strings(addrStrs)
// Update or delete accounts.
for _, addrStr := range addrStrs {
account, deleted := unpack(vas.accounts[addrStr])
if deleted {
removed := vas.state.RemoveAccount(account.Address.Address())
if !removed {
panic(Fmt("Could not remove account to be deleted: %X", account.Address))
}
} else {
if account == nil {
panic(Fmt("Account should not be nil for addr: %X", account.Address))
}
vas.state.UpdateAccount(toStateAccount(account))
}
}
// Determine order for storage updates
// The address comes first so it'll be grouped.
storageKeyStrs := []string{}
for keyStr := range vas.storage {
storageKeyStrs = append(storageKeyStrs, keyStr)
}
sort.Strings(storageKeyStrs)
// Update storage for all account/key.
storage := merkle.NewIAVLTree(
binary.BasicCodec, // TODO change
binary.BasicCodec, // TODO change
1024, // TODO change.
vas.state.DB,
)
var currentAccount *vm.Account
var deleted bool
for _, storageKey := range storageKeyStrs {
value := vas.storage[storageKey]
addrKeyBytes := []byte(storageKey)
addr := addrKeyBytes[:32]
key := addrKeyBytes[32:]
if currentAccount == nil || !bytes.Equal(currentAccount.Address[:], addr) {
currentAccount, deleted = unpack(vas.accounts[string(addr)])
if deleted {
continue
}
var storageRoot []byte
if currentAccount.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = currentAccount.StorageRoot.Bytes()
}
storage.Load(storageRoot)
}
if value.IsZero() {
_, removed := storage.Remove(key)
if !removed {
panic(Fmt("Storage could not be removed for addr: %X @ %X", addr, key))
}
} else {
storage.Set(key, value)
}
}
// TODO support logs, add them to the state somehow.
}
func (vas *VMAppState) AddLog(log *vm.Log) {
vas.logs = append(vas.logs, log)
}
//-----------------------------------------------------------------------------
// Convenience function to return address of new contract
func NewContractAddress(caller []byte, nonce uint64) []byte {
temp := make([]byte, 32+8)
copy(temp, caller)
vm.PutUint64(temp[32:], nonce)
return sha3.Sha3(temp)[:20]
}

View File

@ -39,7 +39,9 @@ func (b *Block) ValidateBasic(lastBlockHeight uint, lastBlockHash []byte,
if !b.LastBlockParts.Equals(lastBlockParts) {
return errors.New("Wrong Block.Header.LastBlockParts")
}
/* TODO: Determine bounds.
/* TODO: Determine bounds
See blockchain/reactor "stopSyncingDurationMinutes"
if !b.Time.After(lastBlockTime) {
return errors.New("Invalid Block.Header.Time")
}
@ -53,19 +55,31 @@ func (b *Block) ValidateBasic(lastBlockHeight uint, lastBlockHash []byte,
return nil
}
// Computes and returns the block hash.
// If the block is incomplete (e.g. missing Header.StateHash)
// then the hash is nil, to prevent the usage of that hash.
func (b *Block) Hash() []byte {
if b.Header == nil || b.Validation == nil || b.Data == nil {
return nil
}
hashes := [][]byte{
b.Header.Hash(),
b.Validation.Hash(),
b.Data.Hash(),
hashHeader := b.Header.Hash()
hashValidation := b.Validation.Hash()
hashData := b.Data.Hash()
// If hashHeader is nil, required fields are missing.
if len(hashHeader) == 0 {
return nil
}
// Merkle hash from sub-hashes.
// Merkle hash from subhashes.
hashes := [][]byte{hashHeader, hashValidation, hashData}
return merkle.HashFromHashes(hashes)
}
func (b *Block) MakePartSet() *PartSet {
return NewPartSetFromData(binary.BinaryBytes(b))
}
// Convenience.
// A nil block never hashes to anything.
// Nothing hashes to a nil hash.
@ -119,7 +133,12 @@ type Header struct {
StateHash []byte
}
// NOTE: hash is nil if required fields are missing.
func (h *Header) Hash() []byte {
if len(h.StateHash) == 0 {
return nil
}
buf := new(bytes.Buffer)
hasher, n, err := sha256.New(), new(int64), new(error)
binary.WriteBinary(h, buf, n, err)

15
types/block_meta.go Normal file
View File

@ -0,0 +1,15 @@
package types
type BlockMeta struct {
Hash []byte // The block hash
Header *Header // The block's Header
Parts PartSetHeader // The PartSetHeader, for transfer
}
func NewBlockMeta(block *Block, blockParts *PartSet) *BlockMeta {
return &BlockMeta{
Hash: block.Hash(),
Header: block.Header,
Parts: blockParts.Header(),
}
}

View File

@ -1,247 +0,0 @@
package types
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
dbm "github.com/tendermint/tendermint/db"
)
/*
Simple low level store for blocks.
There are three types of information stored:
- BlockMeta: Meta information about each block
- Block part: Parts of each block, aggregated w/ PartSet
- Validation: The Validation part of each block, for gossiping commit votes
Currently the commit signatures are duplicated in the Block parts as
well as the Validation. In the future this may change, perhaps by moving
the Validation data outside the Block.
*/
type BlockStore struct {
height uint
db dbm.DB
}
func NewBlockStore(db dbm.DB) *BlockStore {
bsjson := LoadBlockStoreStateJSON(db)
return &BlockStore{
height: bsjson.Height,
db: db,
}
}
// Height() returns the last known contiguous block height.
func (bs *BlockStore) Height() uint {
return bs.height
}
func (bs *BlockStore) GetReader(key []byte) io.Reader {
bytez := bs.db.Get(key)
if bytez == nil {
return nil
}
return bytes.NewReader(bytez)
}
func (bs *BlockStore) LoadBlock(height uint) *Block {
var n int64
var err error
r := bs.GetReader(calcBlockMetaKey(height))
if r == nil {
panic(Fmt("Block does not exist at height %v", height))
}
meta := binary.ReadBinary(&BlockMeta{}, r, &n, &err).(*BlockMeta)
if err != nil {
panic(Fmt("Error reading block meta: %v", err))
}
bytez := []byte{}
for i := uint(0); i < meta.Parts.Total; i++ {
part := bs.LoadBlockPart(height, i)
bytez = append(bytez, part.Bytes...)
}
block := binary.ReadBinary(&Block{}, bytes.NewReader(bytez), &n, &err).(*Block)
if err != nil {
panic(Fmt("Error reading block: %v", err))
}
return block
}
func (bs *BlockStore) LoadBlockPart(height uint, index uint) *Part {
var n int64
var err error
r := bs.GetReader(calcBlockPartKey(height, index))
if r == nil {
panic(Fmt("BlockPart does not exist for height %v index %v", height, index))
}
part := binary.ReadBinary(&Part{}, r, &n, &err).(*Part)
if err != nil {
panic(Fmt("Error reading block part: %v", err))
}
return part
}
func (bs *BlockStore) LoadBlockMeta(height uint) *BlockMeta {
var n int64
var err error
r := bs.GetReader(calcBlockMetaKey(height))
if r == nil {
panic(Fmt("BlockMeta does not exist for height %v", height))
}
meta := binary.ReadBinary(&BlockMeta{}, r, &n, &err).(*BlockMeta)
if err != nil {
panic(Fmt("Error reading block meta: %v", err))
}
return meta
}
// NOTE: the Commit-vote heights are for the block at `height-1`
// Since these are included in the subsequent block, the height
// is off by 1.
func (bs *BlockStore) LoadBlockValidation(height uint) *Validation {
var n int64
var err error
r := bs.GetReader(calcBlockValidationKey(height))
if r == nil {
panic(Fmt("BlockValidation does not exist for height %v", height))
}
validation := binary.ReadBinary(&Validation{}, r, &n, &err).(*Validation)
if err != nil {
panic(Fmt("Error reading validation: %v", err))
}
return validation
}
// NOTE: the Commit-vote heights are for the block at `height`
func (bs *BlockStore) LoadSeenValidation(height uint) *Validation {
var n int64
var err error
r := bs.GetReader(calcSeenValidationKey(height))
if r == nil {
panic(Fmt("SeenValidation does not exist for height %v", height))
}
validation := binary.ReadBinary(&Validation{}, r, &n, &err).(*Validation)
if err != nil {
panic(Fmt("Error reading validation: %v", err))
}
return validation
}
// blockParts: Must be parts of the block
// seenValidation: The +2/3 commits that were seen which finalized the height.
// If all the nodes restart after committing a block,
// we need this to reload the commits to catch-up nodes to the
// most recent height. Otherwise they'd stall at H-1.
// Also good to have to debug consensus issues & punish wrong-signers
// whose commits weren't included in the block.
func (bs *BlockStore) SaveBlock(block *Block, blockParts *PartSet, seenValidation *Validation) {
height := block.Height
if height != bs.height+1 {
panic(Fmt("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.height+1, height))
}
if !blockParts.IsComplete() {
panic(Fmt("BlockStore can only save complete block part sets"))
}
// Save block meta
meta := makeBlockMeta(block, blockParts)
metaBytes := binary.BinaryBytes(meta)
bs.db.Set(calcBlockMetaKey(height), metaBytes)
// Save block parts
for i := uint(0); i < blockParts.Total(); i++ {
bs.saveBlockPart(height, i, blockParts.GetPart(i))
}
// Save block validation (duplicate and separate from the Block)
blockValidationBytes := binary.BinaryBytes(block.Validation)
bs.db.Set(calcBlockValidationKey(height), blockValidationBytes)
// Save seen validation (seen +2/3 commits)
seenValidationBytes := binary.BinaryBytes(seenValidation)
bs.db.Set(calcSeenValidationKey(height), seenValidationBytes)
// Save new BlockStoreStateJSON descriptor
BlockStoreStateJSON{Height: height}.Save(bs.db)
// Done!
bs.height = height
}
func (bs *BlockStore) saveBlockPart(height uint, index uint, part *Part) {
if height != bs.height+1 {
panic(Fmt("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.height+1, height))
}
partBytes := binary.BinaryBytes(part)
bs.db.Set(calcBlockPartKey(height, index), partBytes)
}
//-----------------------------------------------------------------------------
type BlockMeta struct {
Hash []byte // The block hash
Header *Header // The block's Header
Parts PartSetHeader // The PartSetHeader, for transfer
}
func makeBlockMeta(block *Block, blockParts *PartSet) *BlockMeta {
return &BlockMeta{
Hash: block.Hash(),
Header: block.Header,
Parts: blockParts.Header(),
}
}
//-----------------------------------------------------------------------------
func calcBlockMetaKey(height uint) []byte {
return []byte(fmt.Sprintf("H:%v", height))
}
func calcBlockPartKey(height uint, partIndex uint) []byte {
return []byte(fmt.Sprintf("P:%v:%v", height, partIndex))
}
func calcBlockValidationKey(height uint) []byte {
return []byte(fmt.Sprintf("V:%v", height))
}
func calcSeenValidationKey(height uint) []byte {
return []byte(fmt.Sprintf("SV:%v", height))
}
//-----------------------------------------------------------------------------
var blockStoreKey = []byte("blockStore")
type BlockStoreStateJSON struct {
Height uint
}
func (bsj BlockStoreStateJSON) Save(db dbm.DB) {
bytes, err := json.Marshal(bsj)
if err != nil {
panic(Fmt("Could not marshal state bytes: %v", err))
}
db.Set(blockStoreKey, bytes)
}
func LoadBlockStoreStateJSON(db dbm.DB) BlockStoreStateJSON {
bytes := db.Get(blockStoreKey)
if bytes == nil {
return BlockStoreStateJSON{
Height: 0,
}
}
bsj := BlockStoreStateJSON{}
err := json.Unmarshal(bytes, &bsj)
if err != nil {
panic(Fmt("Could not unmarshal bytes: %X", bytes))
}
return bsj
}

View File

@ -1,35 +0,0 @@
package vm
import (
"encoding/binary"
)
func Uint64ToWord(i uint64) Word {
word := Word{}
PutUint64(word[:], i)
return word
}
func BytesToWord(bz []byte) Word {
word := Word{}
copy(word[:], bz)
return word
}
func LeftPadWord(bz []byte) (word Word) {
copy(word[32-len(bz):], bz)
return
}
func RightPadWord(bz []byte) (word Word) {
copy(word[:], bz)
return
}
func GetUint64(word Word) uint64 {
return binary.LittleEndian.Uint64(word[:])
}
func PutUint64(dest []byte, i uint64) {
binary.LittleEndian.PutUint64(dest, i)
}

View File

@ -3,7 +3,6 @@ package vm
const (
GasSha3 uint64 = 1
GasGetAccount uint64 = 1
GasStorageCreate uint64 = 1
GasStorageUpdate uint64 = 1
GasStackOp uint64 = 1

View File

@ -3,19 +3,18 @@ package vm
import (
"code.google.com/p/go.crypto/ripemd160"
"crypto/sha256"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm/secp256k1"
"github.com/tendermint/tendermint/vm/sha3"
. "github.com/tendermint/tendermint/common"
)
var nativeContracts = make(map[Word]NativeContract)
var nativeContracts = make(map[Word256]NativeContract)
func init() {
nativeContracts[Uint64ToWord(1)] = ecrecoverFunc
nativeContracts[Uint64ToWord(2)] = sha256Func
nativeContracts[Uint64ToWord(3)] = ripemd160Func
nativeContracts[Uint64ToWord(4)] = identityFunc
nativeContracts[Uint64ToWord256(1)] = ecrecoverFunc
nativeContracts[Uint64ToWord256(2)] = sha256Func
nativeContracts[Uint64ToWord256(3)] = ripemd160Func
nativeContracts[Uint64ToWord256(4)] = identityFunc
}
//-----------------------------------------------------------------------------

View File

@ -2,11 +2,12 @@ package vm
import (
"fmt"
. "github.com/tendermint/tendermint/common"
)
// Not goroutine safe
type Stack struct {
data []Word
data []Word256
ptr int
gas *uint64
@ -15,7 +16,7 @@ type Stack struct {
func NewStack(capacity int, gas *uint64, err *error) *Stack {
return &Stack{
data: make([]Word, capacity),
data: make([]Word256, capacity),
ptr: 0,
gas: gas,
err: err,
@ -36,7 +37,7 @@ func (st *Stack) setErr(err error) {
}
}
func (st *Stack) Push(d Word) {
func (st *Stack) Push(d Word256) {
st.useGas(GasStackOp)
if st.ptr == cap(st.data) {
st.setErr(ErrDataStackOverflow)
@ -50,18 +51,18 @@ func (st *Stack) PushBytes(bz []byte) {
if len(bz) != 32 {
panic("Invalid bytes size: expected 32")
}
st.Push(BytesToWord(bz))
st.Push(RightPadWord256(bz))
}
func (st *Stack) Push64(i uint64) {
st.Push(Uint64ToWord(i))
st.Push(Uint64ToWord256(i))
}
func (st *Stack) Pop() Word {
func (st *Stack) Pop() Word256 {
st.useGas(GasStackOp)
if st.ptr == 0 {
st.setErr(ErrDataStackUnderflow)
return Zero
return Zero256
}
st.ptr--
return st.data[st.ptr]
@ -72,7 +73,7 @@ func (st *Stack) PopBytes() []byte {
}
func (st *Stack) Pop64() uint64 {
return GetUint64(st.Pop())
return GetUint64(st.Pop().Bytes())
}
func (st *Stack) Len() int {
@ -100,7 +101,7 @@ func (st *Stack) Dup(n int) {
}
// Not an opcode, costs no gas.
func (st *Stack) Peek() Word {
func (st *Stack) Peek() Word256 {
return st.data[st.ptr-1]
}

View File

@ -1,8 +1,6 @@
package main
package vm
import (
"fmt"
. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/vm"
"github.com/tendermint/tendermint/vm/sha3"
@ -10,41 +8,39 @@ import (
type FakeAppState struct {
accounts map[string]*Account
storage map[string]Word
storage map[string]Word256
logs []*Log
}
func (fas *FakeAppState) GetAccount(addr Word) (*Account, error) {
func (fas *FakeAppState) GetAccount(addr Word256) *Account {
account := fas.accounts[addr.String()]
if account != nil {
return account, nil
return account
} else {
return nil, Errorf("Invalid account addr: %v", addr)
panic(Fmt("Invalid account addr: %X", addr))
}
}
func (fas *FakeAppState) UpdateAccount(account *Account) error {
func (fas *FakeAppState) UpdateAccount(account *Account) {
_, ok := fas.accounts[account.Address.String()]
if !ok {
return Errorf("Invalid account addr: %v", account.Address.String())
panic(Fmt("Invalid account addr: %X", account.Address))
} else {
// Nothing to do
return nil
}
}
func (fas *FakeAppState) DeleteAccount(account *Account) error {
func (fas *FakeAppState) RemoveAccount(account *Account) {
_, ok := fas.accounts[account.Address.String()]
if !ok {
return Errorf("Invalid account addr: %v", account.Address.String())
panic(Fmt("Invalid account addr: %X", account.Address))
} else {
// Delete account
// Remove account
delete(fas.accounts, account.Address.String())
return nil
}
}
func (fas *FakeAppState) CreateAccount(creator *Account) (*Account, error) {
func (fas *FakeAppState) CreateAccount(creator *Account) *Account {
addr := createAddress(creator)
account := fas.accounts[addr.String()]
if account == nil {
@ -53,75 +49,46 @@ func (fas *FakeAppState) CreateAccount(creator *Account) (*Account, error) {
Balance: 0,
Code: nil,
Nonce: 0,
StorageRoot: Zero,
}, nil
StorageRoot: Zero256,
}
} else {
return nil, Errorf("Invalid account addr: %v", addr)
panic(Fmt("Invalid account addr: %X", addr))
}
}
func (fas *FakeAppState) GetStorage(addr Word, key Word) (Word, error) {
func (fas *FakeAppState) GetStorage(addr Word256, key Word256) Word256 {
_, ok := fas.accounts[addr.String()]
if !ok {
return Zero, Errorf("Invalid account addr: %v", addr)
panic(Fmt("Invalid account addr: %X", addr))
}
value, ok := fas.storage[addr.String()+key.String()]
if ok {
return value, nil
return value
} else {
return Zero, nil
return Zero256
}
}
func (fas *FakeAppState) SetStorage(addr Word, key Word, value Word) (bool, error) {
func (fas *FakeAppState) SetStorage(addr Word256, key Word256, value Word256) {
_, ok := fas.accounts[addr.String()]
if !ok {
return false, Errorf("Invalid account addr: %v", addr)
panic(Fmt("Invalid account addr: %X", addr))
}
_, ok = fas.storage[addr.String()+key.String()]
fas.storage[addr.String()+key.String()] = value
return ok, nil
}
func (fas *FakeAppState) AddLog(log *Log) {
fas.logs = append(fas.logs, log)
}
func main() {
appState := &FakeAppState{
accounts: make(map[string]*Account),
storage: make(map[string]Word),
logs: nil,
}
params := Params{
BlockHeight: 0,
BlockHash: Zero,
BlockTime: 0,
GasLimit: 0,
}
ourVm := NewVM(appState, params, Zero)
// Create accounts
account1 := &Account{
Address: Uint64ToWord(100),
}
account2 := &Account{
Address: Uint64ToWord(101),
}
var gas uint64 = 1000
output, err := ourVm.Call(account1, account2, []byte{0x5B, 0x60, 0x00, 0x56}, []byte{}, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
}
// Creates a 20 byte address and bumps the nonce.
func createAddress(creator *Account) Word {
func createAddress(creator *Account) Word256 {
nonce := creator.Nonce
creator.Nonce += 1
temp := make([]byte, 32+8)
copy(temp, creator.Address[:])
PutUint64(temp[32:], nonce)
return RightPadWord(sha3.Sha3(temp)[:20])
return RightPadWord256(sha3.Sha3(temp)[:20])
}

99
vm/test/vm_test.go Normal file
View File

@ -0,0 +1,99 @@
package vm
import (
"crypto/rand"
"encoding/hex"
"fmt"
"strings"
"testing"
"time"
. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/vm"
)
func newAppState() *FakeAppState {
return &FakeAppState{
accounts: make(map[string]*Account),
storage: make(map[string]Word256),
logs: nil,
}
}
func newParams() Params {
return Params{
BlockHeight: 0,
BlockHash: Zero256,
BlockTime: 0,
GasLimit: 0,
}
}
func makeBytes(n int) []byte {
b := make([]byte, n)
rand.Read(b)
return b
}
func TestVM(t *testing.T) {
ourVm := NewVM(newAppState(), newParams(), Zero256)
// Create accounts
account1 := &Account{
Address: Uint64ToWord256(100),
}
account2 := &Account{
Address: Uint64ToWord256(101),
}
var gas uint64 = 1000
N := []byte{0xff, 0xff}
// Loop N times
code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)}
for i := 0; i < len(N); i++ {
code = append(code, N[i])
}
code = append(code, []byte{0x60, 0x20, 0x51, 0x12, 0x15, 0x60, byte(0x1b + len(N)), 0x57, 0x60, 0x01, 0x60, 0x20, 0x51, 0x01, 0x60, 0x20, 0x52, 0x60, 0x05, 0x56, 0x5B}...)
start := time.Now()
output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
fmt.Println("Call took:", time.Since(start))
}
func TestSubcurrency(t *testing.T) {
st := newAppState()
// Create accounts
account1 := &Account{
Address: RightPadWord256(makeBytes(20)),
}
account2 := &Account{
Address: RightPadWord256(makeBytes(20)),
}
st.accounts[account1.Address.String()] = account1
st.accounts[account2.Address.String()] = account2
ourVm := NewVM(st, newParams(), Zero256)
var gas uint64 = 1000
code_parts := []string{"620f42403355",
"7c0100000000000000000000000000000000000000000000000000000000",
"600035046315cf268481141561004657",
"6004356040526040515460605260206060f35b63693200ce81141561008757",
"60043560805260243560a052335460c0523360e05260a05160c05112151561008657",
"60a05160c0510360e0515560a0516080515401608051555b5b505b6000f3"}
code, _ := hex.DecodeString(strings.Join(code_parts, ""))
fmt.Printf("Code: %x\n", code)
data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005")
output, err := ourVm.Call(account1, account2, code, data, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
}
/*
// infinite loop
code := []byte{0x5B, 0x60, 0x00, 0x56}
// mstore
code := []byte{0x60, 0x00, 0x60, 0x20}
// mstore, mload
code := []byte{0x60, 0x01, 0x60, 0x20, 0x52, 0x60, 0x20, 0x51}
*/

View File

@ -1,44 +1,25 @@
package vm
import ()
import (
. "github.com/tendermint/tendermint/common"
)
const (
defaultDataStackCapacity = 10
)
var (
Zero = Word{0}
One = Word{1}
)
type Word [32]byte
func (w Word) String() string { return string(w[:]) }
func (w Word) Copy() Word { return w }
func (w Word) Bytes() []byte { return w[:] } // copied.
func (w Word) Address() []byte { return w[:20] }
func (w Word) IsZero() bool {
accum := byte(0)
for _, byt := range w {
accum |= byt
}
return accum == 0
}
//-----------------------------------------------------------------------------
type Account struct {
Address Word
Address Word256
Balance uint64
Code []byte
Nonce uint64
StorageRoot Word
StorageRoot Word256
Other interface{} // For holding all other data.
}
type Log struct {
Address Word
Topics []Word
Address Word256
Topics []Word256
Data []byte
Height uint64
}
@ -46,14 +27,14 @@ type Log struct {
type AppState interface {
// Accounts
GetAccount(addr Word) (*Account, error)
UpdateAccount(*Account) error
DeleteAccount(*Account) error
CreateAccount(*Account) (*Account, error)
GetAccount(addr Word256) *Account
UpdateAccount(*Account)
RemoveAccount(*Account)
CreateAccount(*Account) *Account
// Storage
GetStorage(Word, Word) (Word, error)
SetStorage(Word, Word, Word) (bool, error) // Setting to Zero is deleting.
GetStorage(Word256, Word256) Word256
SetStorage(Word256, Word256, Word256) // Setting to Zero is deleting.
// Logs
AddLog(*Log)
@ -61,7 +42,7 @@ type AppState interface {
type Params struct {
BlockHeight uint64
BlockHash Word
BlockHash Word256
BlockTime int64
GasLimit uint64
}

353
vm/vm.go
View File

@ -3,13 +3,14 @@ package vm
import (
"errors"
"fmt"
"math"
"math/big"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm/sha3"
)
var (
ErrUnknownAddress = errors.New("Unknown address")
ErrInsufficientBalance = errors.New("Insufficient balance")
ErrInvalidJumpDest = errors.New("Invalid jump dest")
ErrInsufficientGas = errors.New("Insuffient gas")
@ -23,21 +24,30 @@ var (
ErrInvalidContract = errors.New("Invalid contract")
)
type Debug bool
const (
dataStackCapacity = 1024
callStackCapacity = 100 // TODO ensure usage.
memoryCapacity = 1024 * 1024 // 1 MB
dbg Debug = true
)
func (d Debug) Printf(s string, a ...interface{}) {
if d {
fmt.Printf(s, a...)
}
}
type VM struct {
appState AppState
params Params
origin Word
origin Word256
callDepth int
}
func NewVM(appState AppState, params Params, origin Word) *VM {
func NewVM(appState AppState, params Params, origin Word256) *VM {
return &VM{
appState: appState,
params: params,
@ -73,7 +83,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga
// Just like Call() but does not transfer 'value' or modify the callDepth.
func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) {
fmt.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input)
dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input)
var (
pc uint64 = 0
@ -89,7 +99,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
}
var op = codeGetOp(code, pc)
fmt.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len())
dbg.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len())
switch op {
@ -97,164 +107,197 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, nil
case ADD: // 0x01
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x + y)
fmt.Printf(" %v + %v = %v\n", x, y, x+y)
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(x + y)
x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
sum := new(big.Int).Add(xb, yb)
stack.Push(RightPadWord256(flip(sum.Bytes())))
dbg.Printf(" %v + %v = %v\n", xb, yb, sum)
case MUL: // 0x02
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x * y)
fmt.Printf(" %v * %v = %v\n", x, y, x*y)
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(x * y)
x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
prod := new(big.Int).Mul(xb, yb)
stack.Push(RightPadWord256(flip(prod.Bytes())))
dbg.Printf(" %v * %v = %v\n", xb, yb, prod)
case SUB: // 0x03
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x - y)
fmt.Printf(" %v - %v = %v\n", x, y, x-y)
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(x - y)
x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
diff := new(big.Int).Sub(xb, yb)
stack.Push(RightPadWord256(flip(diff.Bytes())))
dbg.Printf(" %v - %v = %v\n", xb, yb, diff)
case DIV: // 0x04
x, y := stack.Pop64(), stack.Pop64()
if y == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(x / y)
x, y := stack.Pop(), stack.Pop()
if y.IsZero() { // TODO
stack.Push(Zero256)
dbg.Printf(" %x / %x = %v (TODO)\n", x, y, 0)
} else {
stack.Push64(x / y)
fmt.Printf(" %v / %v = %v\n", x, y, x/y)
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
div := new(big.Int).Div(xb, yb)
stack.Push(RightPadWord256(flip(div.Bytes())))
dbg.Printf(" %v / %v = %v\n", xb, yb, div)
}
case SDIV: // 0x05
// TODO ... big?
x, y := int64(stack.Pop64()), int64(stack.Pop64())
if y == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
stack.Push(Zero256)
dbg.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
} else {
stack.Push64(uint64(x / y))
fmt.Printf(" %v / %v = %v\n", x, y, x/y)
dbg.Printf(" %v / %v = %v\n", x, y, x/y)
}
case MOD: // 0x06
x, y := stack.Pop64(), stack.Pop64()
if y == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
//x, y := stack.Pop64(), stack.Pop64()
x, y := stack.Pop(), stack.Pop()
if y.IsZero() { // TODO
stack.Push(Zero256)
dbg.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
} else {
stack.Push64(x % y)
fmt.Printf(" %v %% %v = %v\n", x, y, x%y)
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
mod := new(big.Int).Mod(xb, yb)
stack.Push(RightPadWord256(flip(mod.Bytes())))
dbg.Printf(" %v %% %v = %v\n", xb, yb, mod)
}
case SMOD: // 0x07
// TODO ... big?
x, y := int64(stack.Pop64()), int64(stack.Pop64())
if y == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
stack.Push(Zero256)
dbg.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
} else {
stack.Push64(uint64(x % y))
fmt.Printf(" %v %% %v = %v\n", x, y, x%y)
dbg.Printf(" %v %% %v = %v\n", x, y, x%y)
}
case ADDMOD: // 0x08
// TODO ... big?
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
if z == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
stack.Push(Zero256)
dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
} else {
stack.Push64(x % y)
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z)
stack.Push64((x + y) % z)
dbg.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z)
}
case MULMOD: // 0x09
// TODO ... big?
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
if z == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
stack.Push(Zero256)
dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
} else {
stack.Push64(x % y)
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z)
stack.Push64((x * y) % z)
dbg.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z)
}
case EXP: // 0x0A
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(ExpUint64(x, y))
fmt.Printf(" %v ** %v = %v\n", x, y, uint64(math.Pow(float64(x), float64(y))))
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(ExpUint64(x, y))
x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
pow := new(big.Int).Exp(xb, yb, big.NewInt(0))
stack.Push(RightPadWord256(flip(pow.Bytes())))
dbg.Printf(" %v ** %v = %v\n", xb, yb, pow)
case SIGNEXTEND: // 0x0B
x, y := stack.Pop64(), stack.Pop64()
res := (y << uint(x)) >> x
stack.Push64(res)
fmt.Printf(" (%v << %v) >> %v = %v\n", y, x, x, res)
dbg.Printf(" (%v << %v) >> %v = %v\n", y, x, x, res)
case LT: // 0x10
x, y := stack.Pop64(), stack.Pop64()
if x < y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v < %v = %v\n", x, y, x < y)
dbg.Printf(" %v < %v = %v\n", x, y, x < y)
case GT: // 0x11
x, y := stack.Pop64(), stack.Pop64()
if x > y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v > %v = %v\n", x, y, x > y)
dbg.Printf(" %v > %v = %v\n", x, y, x > y)
case SLT: // 0x12
x, y := int64(stack.Pop64()), int64(stack.Pop64())
if x < y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v < %v = %v\n", x, y, x < y)
dbg.Printf(" %v < %v = %v\n", x, y, x < y)
case SGT: // 0x13
x, y := int64(stack.Pop64()), int64(stack.Pop64())
if x > y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v > %v = %v\n", x, y, x > y)
dbg.Printf(" %v > %v = %v\n", x, y, x > y)
case EQ: // 0x14
x, y := stack.Pop64(), stack.Pop64()
if x > y {
if x == y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v == %v = %v\n", x, y, x == y)
dbg.Printf(" %v == %v = %v\n", x, y, x == y)
case ISZERO: // 0x15
x := stack.Pop64()
if x == 0 {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v == 0 = %v\n", x, x == 0)
dbg.Printf(" %v == 0 = %v\n", x, x == 0)
case AND: // 0x16
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x & y)
fmt.Printf(" %v & %v = %v\n", x, y, x&y)
dbg.Printf(" %v & %v = %v\n", x, y, x&y)
case OR: // 0x17
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x | y)
fmt.Printf(" %v | %v = %v\n", x, y, x|y)
dbg.Printf(" %v | %v = %v\n", x, y, x|y)
case XOR: // 0x18
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x ^ y)
fmt.Printf(" %v ^ %v = %v\n", x, y, x^y)
dbg.Printf(" %v ^ %v = %v\n", x, y, x^y)
case NOT: // 0x19
x := stack.Pop64()
stack.Push64(^x)
fmt.Printf(" !%v = %v\n", x, ^x)
dbg.Printf(" !%v = %v\n", x, ^x)
case BYTE: // 0x1A
idx, val := stack.Pop64(), stack.Pop()
@ -263,7 +306,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
res = val[idx]
}
stack.Push64(uint64(res))
fmt.Printf(" => 0x%X\n", res)
dbg.Printf(" => 0x%X\n", res)
case SHA3: // 0x20
if ok = useGas(gas, GasSha3); !ok {
@ -276,36 +319,36 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
}
data = sha3.Sha3(data)
stack.PushBytes(data)
fmt.Printf(" => (%v) %X\n", size, data)
dbg.Printf(" => (%v) %X\n", size, data)
case ADDRESS: // 0x30
stack.Push(callee.Address)
fmt.Printf(" => %X\n", callee.Address)
dbg.Printf(" => %X\n", callee.Address)
case BALANCE: // 0x31
addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas)
}
account, err_ := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported.
if err_ != nil {
return nil, firstErr(err, err_)
acc := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported.
if acc == nil {
return nil, firstErr(err, ErrUnknownAddress)
}
balance := account.Balance
balance := acc.Balance
stack.Push64(balance)
fmt.Printf(" => %v (%X)\n", balance, addr)
dbg.Printf(" => %v (%X)\n", balance, addr)
case ORIGIN: // 0x32
stack.Push(vm.origin)
fmt.Printf(" => %X\n", vm.origin)
dbg.Printf(" => %X\n", vm.origin)
case CALLER: // 0x33
stack.Push(caller.Address)
fmt.Printf(" => %X\n", caller.Address)
dbg.Printf(" => %X\n", caller.Address)
case CALLVALUE: // 0x34
stack.Push64(value)
fmt.Printf(" => %v\n", value)
dbg.Printf(" => %v\n", value)
case CALLDATALOAD: // 0x35
offset := stack.Pop64()
@ -313,12 +356,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok {
return nil, firstErr(err, ErrInputOutOfBounds)
}
stack.Push(RightPadWord(data))
fmt.Printf(" => 0x%X\n", data)
stack.Push(RightPadWord256(data))
dbg.Printf(" => 0x%X\n", data)
case CALLDATASIZE: // 0x36
stack.Push64(uint64(len(input)))
fmt.Printf(" => %d\n", len(input))
dbg.Printf(" => %d\n", len(input))
case CALLDATACOPY: // 0x37
memOff := stack.Pop64()
@ -333,18 +376,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data)
dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data)
case CODESIZE: // 0x38
l := uint64(len(code))
stack.Push64(l)
fmt.Printf(" => %d\n", l)
dbg.Printf(" => %d\n", l)
case CODECOPY: // 0x39
memOff := stack.Pop64()
codeOff := stack.Pop64()
length := stack.Pop64()
fmt.Println("CODECOPY: codeOff, length, codelength", codeOff, length, len(code))
data, ok := subslice(code, codeOff, length, false)
if !ok {
return nil, firstErr(err, ErrCodeOutOfBounds)
@ -354,36 +396,36 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
case GASPRICE_DEPRECATED: // 0x3A
stack.Push(Zero)
fmt.Printf(" => %X (GASPRICE IS DEPRECATED)\n")
stack.Push(Zero256)
dbg.Printf(" => %X (GASPRICE IS DEPRECATED)\n")
case EXTCODESIZE: // 0x3B
addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas)
}
account, err_ := vm.appState.GetAccount(addr)
if err_ != nil {
return nil, firstErr(err, err_)
acc := vm.appState.GetAccount(addr)
if acc == nil {
return nil, firstErr(err, ErrUnknownAddress)
}
code := account.Code
code := acc.Code
l := uint64(len(code))
stack.Push64(l)
fmt.Printf(" => %d\n", l)
dbg.Printf(" => %d\n", l)
case EXTCODECOPY: // 0x3C
addr := stack.Pop()
if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas)
}
account, err_ := vm.appState.GetAccount(addr)
if err_ != nil {
return nil, firstErr(err, err_)
acc := vm.appState.GetAccount(addr)
if acc == nil {
return nil, firstErr(err, ErrUnknownAddress)
}
code := account.Code
code := acc.Code
memOff := stack.Pop64()
codeOff := stack.Pop64()
length := stack.Pop64()
@ -396,33 +438,33 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
case BLOCKHASH: // 0x40
stack.Push(Zero)
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
stack.Push(Zero256)
dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
case COINBASE: // 0x41
stack.Push(Zero)
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
stack.Push(Zero256)
dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
case TIMESTAMP: // 0x42
time := vm.params.BlockTime
stack.Push64(uint64(time))
fmt.Printf(" => 0x%X\n", time)
dbg.Printf(" => 0x%X\n", time)
case BLOCKHEIGHT: // 0x43
number := uint64(vm.params.BlockHeight)
stack.Push64(number)
fmt.Printf(" => 0x%X\n", number)
dbg.Printf(" => 0x%X\n", number)
case GASLIMIT: // 0x45
stack.Push64(vm.params.GasLimit)
fmt.Printf(" => %v\n", vm.params.GasLimit)
dbg.Printf(" => %v\n", vm.params.GasLimit)
case POP: // 0x50
stack.Pop()
fmt.Printf(" => %v\n", vm.params.GasLimit)
dbg.Printf(" => %v\n", vm.params.GasLimit)
case MLOAD: // 0x51
offset := stack.Pop64()
@ -430,17 +472,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
stack.Push(RightPadWord(data))
fmt.Printf(" => 0x%X\n", data)
stack.Push(RightPadWord256(data))
dbg.Printf(" => 0x%X\n", data)
case MSTORE: // 0x52
offset, data := stack.Pop64(), stack.Pop()
dest, ok := subslice(memory, offset, 32, true)
dest, ok := subslice(memory, offset, 32, false)
if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
copy(dest, data[:])
fmt.Printf(" => 0x%X\n", data)
copy(dest, flip(data[:]))
dbg.Printf(" => 0x%X\n", data)
case MSTORE8: // 0x53
offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF)
@ -448,26 +490,21 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
memory[offset] = val
fmt.Printf(" => [%v] 0x%X\n", offset, val)
dbg.Printf(" => [%v] 0x%X\n", offset, val)
case SLOAD: // 0x54
loc := stack.Pop()
data, _ := vm.appState.GetStorage(callee.Address, loc)
stack.Push(data)
fmt.Printf(" {0x%X : 0x%X}\n", loc, data)
data := vm.appState.GetStorage(callee.Address, loc)
stack.Push(flipWord(data))
dbg.Printf(" {0x%X : 0x%X}\n", loc, data)
case SSTORE: // 0x55
loc, data := stack.Pop(), stack.Pop()
updated, err_ := vm.appState.SetStorage(callee.Address, loc, data)
if err = firstErr(err, err_); err != nil {
return nil, err
}
if updated {
loc = flipWord(loc)
data = flipWord(data)
vm.appState.SetStorage(callee.Address, loc, data)
useGas(gas, GasStorageUpdate)
} else {
useGas(gas, GasStorageCreate)
}
fmt.Printf(" {0x%X : 0x%X}\n", loc, data)
dbg.Printf(" {0x%X : 0x%X}\n", loc, data)
case JUMP: // 0x56
err = jump(code, stack.Pop64(), &pc)
@ -479,7 +516,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
err = jump(code, pos, &pc)
continue
}
fmt.Printf(" ~> false\n")
dbg.Printf(" ~> false\n")
case PC: // 0x58
stack.Push64(pc)
@ -489,10 +526,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
case GAS: // 0x5A
stack.Push64(*gas)
fmt.Printf(" => %X\n", *gas)
dbg.Printf(" => %X\n", *gas)
case JUMPDEST: // 0x5B
fmt.Printf("\n")
dbg.Printf("\n")
// Do nothing
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
@ -501,24 +538,24 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok {
return nil, firstErr(err, ErrCodeOutOfBounds)
}
res := RightPadWord(codeSegment)
res := RightPadWord256(codeSegment)
stack.Push(res)
pc += a
fmt.Printf(" => 0x%X\n", res)
dbg.Printf(" => 0x%X\n", res)
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
n := int(op - DUP1 + 1)
stack.Dup(n)
fmt.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes())
dbg.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes())
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
n := int(op - SWAP1 + 2)
stack.Swap(n)
fmt.Printf(" => [%d]\n", n)
dbg.Printf(" => [%d]\n", n)
case LOG0, LOG1, LOG2, LOG3, LOG4:
n := int(op - LOG0)
topics := make([]Word, n)
topics := make([]Word256, n)
offset, size := stack.Pop64(), stack.Pop64()
for i := 0; i < n; i++ {
topics[i] = stack.Pop()
@ -534,7 +571,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
vm.params.BlockHeight,
}
vm.appState.AddLog(log)
fmt.Printf(" => %v\n", log)
dbg.Printf(" => %v\n", log)
case CREATE: // 0xF0
contractValue := stack.Pop64()
@ -551,27 +588,22 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
// TODO charge for gas to create account _ the code length * GasCreateByte
newAccount, err := vm.appState.CreateAccount(callee)
if err != nil {
stack.Push(Zero)
fmt.Printf(" (*) 0x0 %v\n", err)
} else {
newAccount := vm.appState.CreateAccount(callee)
// Run the input to get the contract code.
ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas)
if err_ != nil {
stack.Push(Zero)
stack.Push(Zero256)
} else {
newAccount.Code = ret // Set the code
stack.Push(newAccount.Address)
}
}
case CALL, CALLCODE: // 0xF1, 0xF2
gasLimit := stack.Pop64()
addr, value := stack.Pop(), stack.Pop64()
inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs
retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs
fmt.Printf(" => %X\n", addr)
dbg.Printf(" => %X\n", addr)
// Get the arguments from the memory
args, ok := subslice(memory, inOffset, inSize, false)
@ -598,22 +630,22 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas)
}
account, err_ := vm.appState.GetAccount(addr)
if err = firstErr(err, err_); err != nil {
return nil, err
acc := vm.appState.GetAccount(addr)
if acc == nil {
return nil, firstErr(err, ErrUnknownAddress)
}
if op == CALLCODE {
ret, err = vm.Call(callee, callee, account.Code, args, value, gas)
ret, err = vm.Call(callee, callee, acc.Code, args, value, gas)
} else {
ret, err = vm.Call(callee, account, account.Code, args, value, gas)
ret, err = vm.Call(callee, acc, acc.Code, args, value, gas)
}
}
// Push result
if err != nil {
stack.Push(Zero)
stack.Push(Zero256)
} else {
stack.Push(One)
stack.Push(One256)
dest, ok := subslice(memory, retOffset, retSize, false)
if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds)
@ -624,7 +656,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
// Handle remaining gas.
*gas += gasLimit
fmt.Printf("resume %X (%v)\n", callee.Address, gas)
dbg.Printf("resume %X (%v)\n", callee.Address, gas)
case RETURN: // 0xF3
offset, size := stack.Pop64(), stack.Pop64()
@ -632,7 +664,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
fmt.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret)
dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret)
return ret, nil
case SUICIDE: // 0xFF
@ -640,20 +672,20 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if ok = useGas(gas, GasGetAccount); !ok {
return nil, firstErr(err, ErrInsufficientGas)
}
// TODO if the receiver is Zero, then make it the fee.
receiver, err_ := vm.appState.GetAccount(addr)
if err = firstErr(err, err_); err != nil {
return nil, err
// TODO if the receiver is , then make it the fee.
receiver := vm.appState.GetAccount(addr)
if receiver == nil {
return nil, firstErr(err, ErrUnknownAddress)
}
balance := callee.Balance
receiver.Balance += balance
vm.appState.UpdateAccount(receiver)
vm.appState.DeleteAccount(callee)
fmt.Printf(" => (%X) %v\n", addr[:4], balance)
vm.appState.RemoveAccount(callee)
dbg.Printf(" => (%X) %v\n", addr[:4], balance)
fallthrough
default:
fmt.Printf("(pc) %-3v Invalid opcode %X\n", pc, op)
dbg.Printf("(pc) %-3v Invalid opcode %X\n", pc, op)
panic(fmt.Errorf("Invalid opcode %X", op))
}
@ -688,10 +720,10 @@ func codeGetOp(code []byte, n uint64) OpCode {
func jump(code []byte, to uint64, pc *uint64) (err error) {
dest := codeGetOp(code, to)
if dest != JUMPDEST {
fmt.Printf(" ~> %v invalid jump dest %v\n", to, dest)
dbg.Printf(" ~> %v invalid jump dest %v\n", to, dest)
return ErrInvalidJumpDest
}
fmt.Printf(" ~> %v\n", to)
dbg.Printf(" ~> %v\n", to)
*pc = to
return nil
}
@ -724,10 +756,25 @@ func transfer(from, to *Account, amount uint64) error {
}
func flip(in []byte) []byte {
l2 := len(in) / 2
flipped := make([]byte, len(in))
for i := 0; i < len(flipped)/2; i++ {
// copy the middle bit (if its even it will get overwritten)
if len(in) != 0 {
flipped[l2] = in[l2]
}
for i := 0; i < l2; i++ {
flipped[i] = in[len(in)-1-i]
flipped[len(in)-1-i] = in[i]
}
return flipped
}
func flipWord(in Word256) Word256 {
word := Word256{}
// copy the middle bit (if its even it will get overwritten)
for i := 0; i < 16; i++ {
word[i] = in[len(in)-1-i]
word[len(in)-1-i] = in[i]
}
return word
}