Merge pull request #507 from nspcc-dev/dbft

network: plug in dBFT library
This commit is contained in:
Roman Khimov 2019-11-27 15:22:40 +03:00 committed by GitHub
commit 2d41450ac9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1426 additions and 153 deletions

View file

@ -69,7 +69,3 @@ services:
- 20334:20334
- 20344:20344
- 20354:20354
depends_on:
- node_one
- node_two
- node_three

View file

@ -105,7 +105,7 @@ env_image: env_vendor
env_up:
@echo "=> Bootup environment"
@docker-compose -f $(DC_FILE) up -d node_four
@docker-compose -f $(DC_FILE) up -d node_one node_two node_three node_four
env_down:
@echo "=> Stop and cleanup environment"

View file

@ -78,6 +78,13 @@ type (
MinPeers int `yaml:"MinPeers"`
Monitoring metrics.PrometheusConfig `yaml:"Monitoring"`
RPC RPCConfig `yaml:"RPC"`
UnlockWallet WalletConfig `yaml:"UnlockWallet"`
}
// WalletConfig is a wallet info.
WalletConfig struct {
Path string `yaml:"Path"`
Password string `yaml:"Password"`
}
// RPCConfig is an RPC service configuration information (to be moved to the rpc package, see #423).

View file

@ -9,10 +9,10 @@ ProtocolConfiguration:
- 03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699
- 02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62
SeedList:
- 172.20.0.1:20331
- 172.20.0.2:20332
- 172.20.0.3:20333
- 172.20.0.4:20334
- 172.200.0.1:20331
- 172.200.0.2:20332
- 172.200.0.3:20333
- 172.200.0.4:20334
SystemFee:
EnrollmentTransaction: 1000
IssueTransaction: 500

19
go.mod
View file

@ -2,31 +2,22 @@ module github.com/CityOfZion/neo-go
require (
github.com/Workiva/go-datastructures v1.0.50
github.com/abiosoft/ishell v2.0.0+incompatible // indirect
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/etcd-io/bbolt v1.3.3
github.com/fatih/color v1.7.0 // indirect
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/go-redis/redis v6.10.2+incompatible
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.9 // indirect
github.com/mr-tron/base58 v1.1.2
github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055
github.com/nspcc-dev/rfc6979 v0.1.0
github.com/onsi/gomega v1.4.2 // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.2.1
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.3.0
github.com/stretchr/testify v1.4.0
github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73
github.com/urfave/cli v1.20.0
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 // indirect
go.etcd.io/bbolt v1.3.3 // indirect
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
go.uber.org/atomic v1.4.0
go.uber.org/zap v1.10.0
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/text v0.3.0
golang.org/x/tools v0.0.0-20180318012157-96caea41033d
gopkg.in/abiosoft/ishell.v2 v2.0.0

19
go.sum
View file

@ -1,3 +1,4 @@
github.com/CityOfZion/neo-go v0.62.1-pre.0.20191114145240-e740fbe708f8/go.mod h1:MJCkWUBhi9pn/CrYO1Q3P687y2KeahrOPS9BD9LDGb0=
github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo=
github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA=
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
@ -81,6 +82,10 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mr-tron/base58 v1.1.2 h1:ZEw4I2EgPKDJ2iEw0cNmLB3ROrEmkOtXIkaG7wZg+78=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055 h1:Rs8zeSFfXOX2RuUjWwsUrQpNbxoWyeXU8lWPlYkxDo0=
github.com/nspcc-dev/dbft v0.0.0-20191125155719-1c35ab053055/go.mod h1:w1Ln2aT+dBlPhLnuZhBV+DfPEdS2CHWWLp5JTScY3bw=
github.com/nspcc-dev/neofs-crypto v0.2.0 h1:ftN+59WqxSWz/RCgXYOfhmltOOqU+udsNQSvN6wkFck=
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
github.com/nspcc-dev/rfc6979 v0.1.0 h1:Lwg7esRRoyK1Up/IN1vAef1EmvrBeMHeeEkek2fAJ6c=
github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
@ -111,11 +116,15 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73 h1:I2drr5K0tykBofr74ZEGliE/Hf6fNkEbcPyFvsy7wZk=
github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
@ -124,12 +133,21 @@ github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 h1:1b6PAtenNyhsmo/
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
@ -143,6 +161,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

101
pkg/consensus/block.go Normal file
View file

@ -0,0 +1,101 @@
package consensus
import (
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/dbft/block"
"github.com/nspcc-dev/dbft/crypto"
)
// neoBlock is a wrapper of core.Block which implements
// methods necessary for dBFT library.
type neoBlock struct {
core.Block
signature []byte
}
var _ block.Block = (*neoBlock)(nil)
// Sign implements block.Block interface.
func (n *neoBlock) Sign(key crypto.PrivateKey) error {
data := n.BlockBase.GetHashableData()
sig, err := key.Sign(data[:])
if err != nil {
return err
}
n.signature = sig
return nil
}
// Verify implements block.Block interface.
func (n *neoBlock) Verify(key crypto.PublicKey, sign []byte) error {
data := n.BlockBase.GetHashableData()
return key.Verify(data, sign)
}
// Transactions implements block.Block interface.
func (n *neoBlock) Transactions() []block.Transaction {
txes := make([]block.Transaction, len(n.Block.Transactions))
for i, tx := range n.Block.Transactions {
txes[i] = tx
}
return txes
}
// SetTransactions implements block.Block interface.
func (n *neoBlock) SetTransactions(txes []block.Transaction) {
n.Block.Transactions = make([]*transaction.Transaction, len(txes))
for i, tx := range txes {
n.Block.Transactions[i] = tx.(*transaction.Transaction)
}
}
// Version implements block.Block interface.
func (n *neoBlock) Version() uint32 { return n.Block.Version }
// SetVersion implements block.Block interface.
func (n *neoBlock) SetVersion(v uint32) { n.Block.Version = v }
// PrevHash implements block.Block interface.
func (n *neoBlock) PrevHash() util.Uint256 { return n.Block.PrevHash }
// SetPrevHash implements block.Block interface.
func (n *neoBlock) SetPrevHash(h util.Uint256) { n.Block.PrevHash = h }
// MerkleRoot implements block.Block interface.
func (n *neoBlock) MerkleRoot() util.Uint256 { return n.Block.MerkleRoot }
// SetMerkleRoot implements block.Block interface.
func (n *neoBlock) SetMerkleRoot(r util.Uint256) { n.Block.MerkleRoot = r }
// Timestamp implements block.Block interface.
func (n *neoBlock) Timestamp() uint32 { return n.Block.Timestamp }
// SetTimestamp implements block.Block interface.
func (n *neoBlock) SetTimestamp(ts uint32) { n.Block.Timestamp = ts }
// Index implements block.Block interface.
func (n *neoBlock) Index() uint32 { return n.Block.Index }
// SetIndex implements block.Block interface.
func (n *neoBlock) SetIndex(i uint32) { n.Block.Index = i }
// ConsensusData implements block.Block interface.
func (n *neoBlock) ConsensusData() uint64 { return n.Block.ConsensusData }
// SetConsensusData implements block.Block interface.
func (n *neoBlock) SetConsensusData(nonce uint64) { n.Block.ConsensusData = nonce }
// NextConsensus implements block.Block interface.
func (n *neoBlock) NextConsensus() util.Uint160 { return n.Block.NextConsensus }
// SetNextConsensus implements block.Block interface.
func (n *neoBlock) SetNextConsensus(h util.Uint160) { n.Block.NextConsensus = h }
// Signature implements block.Block interface.
func (n *neoBlock) Signature() []byte { return n.signature }

View file

@ -0,0 +1,48 @@
package consensus
import (
"crypto/rand"
"testing"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/dbft/block"
"github.com/nspcc-dev/dbft/crypto"
"github.com/stretchr/testify/require"
)
func TestNeoBlock_Sign(t *testing.T) {
b := new(neoBlock)
priv, pub := crypto.Generate(rand.Reader)
require.NoError(t, b.Sign(priv))
require.NoError(t, b.Verify(pub, b.Signature()))
}
func TestNeoBlock_Setters(t *testing.T) {
b := new(neoBlock)
b.SetVersion(1)
require.EqualValues(t, 1, b.Version())
b.SetIndex(12)
require.EqualValues(t, 12, b.Index())
b.SetTimestamp(777)
require.EqualValues(t, 777, b.Timestamp())
b.SetConsensusData(456)
require.EqualValues(t, 456, b.ConsensusData())
b.SetMerkleRoot(util.Uint256{1, 2, 3, 4})
require.Equal(t, util.Uint256{1, 2, 3, 4}, b.MerkleRoot())
b.SetNextConsensus(util.Uint160{9, 2})
require.Equal(t, util.Uint160{9, 2}, b.NextConsensus())
b.SetPrevHash(util.Uint256{9, 8, 7})
require.Equal(t, util.Uint256{9, 8, 7}, b.PrevHash())
txx := []block.Transaction{newMinerTx(123)}
b.SetTransactions(txx)
require.Equal(t, txx, b.Transactions())
}

View file

@ -17,6 +17,11 @@ type relayCache struct {
queue *list.List
}
// hashable is a type of items which can be stored in the relayCache.
type hashable interface {
Hash() util.Uint256
}
func newFIFOCache(capacity int) *relayCache {
return &relayCache{
RWMutex: new(sync.RWMutex),
@ -28,7 +33,7 @@ func newFIFOCache(capacity int) *relayCache {
}
// Add adds payload into a cache if it doesn't already exist.
func (c *relayCache) Add(p *Payload) {
func (c *relayCache) Add(p hashable) {
c.Lock()
defer c.Unlock()
@ -40,21 +45,29 @@ func (c *relayCache) Add(p *Payload) {
if c.queue.Len() >= c.maxCap {
first := c.queue.Front()
c.queue.Remove(first)
delete(c.elems, first.Value.(*Payload).Hash())
delete(c.elems, first.Value.(hashable).Hash())
}
e := c.queue.PushBack(p)
c.elems[h] = e
}
// Has checks if an item is already in cache.
func (c *relayCache) Has(h util.Uint256) bool {
c.RLock()
defer c.RUnlock()
return c.elems[h] != nil
}
// Get returns payload with the specified hash from cache.
func (c *relayCache) Get(h util.Uint256) *Payload {
func (c *relayCache) Get(h util.Uint256) hashable {
c.RLock()
defer c.RUnlock()
e, ok := c.elems[h]
if !ok {
return nil
return hashable(nil)
}
return e.Value.(*Payload)
return e.Value.(hashable)
}

View file

@ -3,6 +3,7 @@ package consensus
import (
"testing"
"github.com/nspcc-dev/dbft/payload"
"github.com/stretchr/testify/require"
)
@ -15,6 +16,7 @@ func TestRelayCache_Add(t *testing.T) {
for i := 1; i < capacity; i++ {
c.Add(&payloads[i])
require.True(t, c.Has(payloads[i].Hash()))
require.Equal(t, i, c.queue.Len())
require.Equal(t, i, len(c.elems))
}
@ -40,7 +42,7 @@ func TestRelayCache_Add(t *testing.T) {
}
// oldest payload was removed
require.Equal(t, (*Payload)(nil), c.Get(payloads[1].Hash()))
require.Equal(t, nil, c.Get(payloads[1].Hash()))
}
func getDifferentPayloads(t *testing.T, n int) (payloads []Payload) {
@ -49,10 +51,10 @@ func getDifferentPayloads(t *testing.T, n int) (payloads []Payload) {
var sign [signatureSize]byte
fillRandom(t, sign[:])
payloads[i].ValidatorIndex = uint16(i)
payloads[i].Type = commitType
payloads[i].SetValidatorIndex(uint16(i))
payloads[i].SetType(payload.MessageType(commitType))
payloads[i].payload = &commit{
Signature: sign,
signature: sign,
}
}

View file

@ -1,19 +1,36 @@
package consensus
import "github.com/CityOfZion/neo-go/pkg/io"
import (
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/nspcc-dev/dbft/payload"
)
// changeView represents dBFT ChangeView message.
type changeView struct {
NewViewNumber byte
Timestamp uint32
newViewNumber byte
timestamp uint32
}
var _ payload.ChangeView = (*changeView)(nil)
// EncodeBinary implements io.Serializable interface.
func (c *changeView) EncodeBinary(w *io.BinWriter) {
w.WriteLE(c.Timestamp)
w.WriteLE(c.timestamp)
}
// DecodeBinary implements io.Serializable interface.
func (c *changeView) DecodeBinary(r *io.BinReader) {
r.ReadLE(&c.Timestamp)
r.ReadLE(&c.timestamp)
}
// NewViewNumber implements payload.ChangeView interface.
func (c changeView) NewViewNumber() byte { return c.newViewNumber }
// SetNewViewNumber implements payload.ChangeView interface.
func (c *changeView) SetNewViewNumber(view byte) { c.newViewNumber = view }
// Timestamp implements payload.ChangeView interface.
func (c changeView) Timestamp() uint32 { return c.timestamp }
// SetTimestamp implements payload.ChangeView interface.
func (c *changeView) SetTimestamp(ts uint32) { c.timestamp = ts }

View file

@ -0,0 +1,17 @@
package consensus
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestChangeView_Setters(t *testing.T) {
var c changeView
c.SetTimestamp(123)
require.EqualValues(t, 123, c.Timestamp())
c.SetNewViewNumber(2)
require.EqualValues(t, 2, c.NewViewNumber())
}

View file

@ -1,22 +1,35 @@
package consensus
import "github.com/CityOfZion/neo-go/pkg/io"
import (
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/nspcc-dev/dbft/payload"
)
// commit represents dBFT Commit message.
type commit struct {
Signature [signatureSize]byte
signature [signatureSize]byte
}
// signatureSize is an rfc6989 signature size in bytes
// without leading byte (0x04, uncompressed)
const signatureSize = 64
var _ payload.Commit = (*commit)(nil)
// EncodeBinary implements io.Serializable interface.
func (c *commit) EncodeBinary(w *io.BinWriter) {
w.WriteBE(c.Signature)
w.WriteBE(c.signature)
}
// DecodeBinary implements io.Serializable interface.
func (c *commit) DecodeBinary(r *io.BinReader) {
r.ReadBE(&c.Signature)
r.ReadBE(&c.signature)
}
// Signature implements payload.Commit interface.
func (c commit) Signature() []byte { return c.signature[:] }
// SetSignature implements payload.Commit interface.
func (c *commit) SetSignature(signature []byte) {
copy(c.signature[:], signature)
}

View file

@ -0,0 +1,16 @@
package consensus
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCommit_Setters(t *testing.T) {
var sign [signatureSize]byte
fillRandom(t, sign[:])
var c commit
c.SetSignature(sign[:])
require.Equal(t, sign[:], c.Signature())
}

View file

@ -1,34 +1,371 @@
package consensus
import "github.com/CityOfZion/neo-go/pkg/util"
import (
"errors"
"math/rand"
"sort"
"time"
"github.com/CityOfZion/neo-go/config"
"github.com/CityOfZion/neo-go/pkg/core"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
"github.com/CityOfZion/neo-go/pkg/wallet"
"github.com/nspcc-dev/dbft"
"github.com/nspcc-dev/dbft/block"
"github.com/nspcc-dev/dbft/crypto"
"github.com/nspcc-dev/dbft/payload"
"go.uber.org/zap"
)
// cacheMaxCapacity is the default cache capacity taken
// from C# implementation https://github.com/neo-project/neo/blob/master/neo/Ledger/Blockchain.cs#L64
const cacheMaxCapacity = 100
// defaultTimePerBlock is a period between blocks which is used in NEO.
const defaultTimePerBlock = 15 * time.Second
// Service represents consensus instance.
type Service interface {
// Start initializes dBFT and starts event loop for consensus service.
// It must be called only when sufficient amount of peers are connected.
Start()
// OnPayload is a callback to notify Service about new received payload.
OnPayload(p *Payload)
// OnTransaction is a callback to notify Service about new received transaction.
OnTransaction(tx *transaction.Transaction)
// GetPayload returns Payload with specified hash if it is present in the local cache.
GetPayload(h util.Uint256) *Payload
}
type service struct {
Config
log *zap.SugaredLogger
// cache is a fifo cache which stores recent payloads.
cache *relayCache
// txx is a fifo cache which stores miner transactions.
txx *relayCache
dbft *dbft.DBFT
// messages and transactions are channels needed to process
// everything in single thread.
messages chan Payload
transactions chan *transaction.Transaction
}
// Config is a configuration for consensus services.
type Config struct {
// Broadcast is a callback which is called to notify server
// about new consensus payload to sent.
Broadcast func(p *Payload)
// Chain is a core.Blockchainer instance.
Chain core.Blockchainer
// RequestTx is a callback to which will be called
// when a node lacks transactions present in a block.
RequestTx func(h ...util.Uint256)
// TimePerBlock minimal time that should pass before next block is accepted.
TimePerBlock time.Duration
// Wallet is a local-node wallet configuration.
Wallet *config.WalletConfig
}
// NewService returns new consensus.Service instance.
func NewService() Service {
return &service{
func NewService(cfg Config) (Service, error) {
log, err := getLogger()
if err != nil {
return nil, err
}
if cfg.TimePerBlock <= 0 {
cfg.TimePerBlock = defaultTimePerBlock
}
srv := &service{
Config: cfg,
log: log.Sugar(),
cache: newFIFOCache(cacheMaxCapacity),
txx: newFIFOCache(cacheMaxCapacity),
messages: make(chan Payload, 100),
}
if cfg.Wallet == nil {
return srv, nil
}
priv, pub := getKeyPair(cfg.Wallet)
srv.dbft = dbft.New(
dbft.WithLogger(srv.log.Desugar()),
dbft.WithSecondsPerBlock(cfg.TimePerBlock),
dbft.WithKeyPair(priv, pub),
dbft.WithTxPerBlock(10000),
dbft.WithRequestTx(cfg.RequestTx),
dbft.WithGetTx(srv.getTx),
dbft.WithGetVerified(srv.getVerifiedTx),
dbft.WithBroadcast(srv.broadcast),
dbft.WithProcessBlock(srv.processBlock),
dbft.WithVerifyBlock(srv.verifyBlock),
dbft.WithGetBlock(srv.getBlock),
dbft.WithWatchOnly(func() bool { return false }),
dbft.WithNewBlock(func() block.Block { return new(neoBlock) }),
dbft.WithCurrentHeight(cfg.Chain.BlockHeight),
dbft.WithCurrentBlockHash(cfg.Chain.CurrentBlockHash),
dbft.WithGetValidators(srv.getValidators),
dbft.WithGetConsensusAddress(srv.getConsensusAddress),
dbft.WithNewConsensusPayload(func() payload.ConsensusPayload { return new(Payload) }),
dbft.WithNewPrepareRequest(func() payload.PrepareRequest { return new(prepareRequest) }),
dbft.WithNewPrepareResponse(func() payload.PrepareResponse { return new(prepareResponse) }),
dbft.WithNewChangeView(func() payload.ChangeView { return new(changeView) }),
dbft.WithNewCommit(func() payload.Commit { return new(commit) }),
)
if srv.dbft == nil {
return nil, errors.New("can't initialize dBFT")
}
return srv, nil
}
var (
_ block.Transaction = (*transaction.Transaction)(nil)
_ block.Block = (*neoBlock)(nil)
)
func (s *service) Start() {
s.dbft.Start()
go s.eventLoop()
}
func (s *service) eventLoop() {
for {
select {
case hv := <-s.dbft.Timer.C():
s.log.Debugf("timer fired (%d,%d)", hv.Height, hv.View)
s.dbft.OnTimeout(hv)
case msg := <-s.messages:
s.log.Debugf("received message from %d", msg.validatorIndex)
s.dbft.OnReceive(&msg)
case tx := <-s.transactions:
s.dbft.OnTransaction(tx)
}
}
}
func getKeyPair(cfg *config.WalletConfig) (crypto.PrivateKey, crypto.PublicKey) {
acc, err := wallet.DecryptAccount(cfg.Path, cfg.Password)
if err != nil {
return nil, nil
}
key := acc.PrivateKey()
if key == nil {
return nil, nil
}
return &privateKey{PrivateKey: key}, &publicKey{PublicKey: key.PublicKey()}
}
// OnPayload handles Payload receive.
func (s *service) OnPayload(p *Payload) {
s.cache.Add(p)
func (s *service) OnPayload(cp *Payload) {
if s.cache.Has(cp.Hash()) {
return
}
s.Config.Broadcast(cp)
s.cache.Add(cp)
if s.dbft == nil {
return
}
// we use switch here because other payloads could be possibly added in future
switch cp.Type() {
case payload.PrepareRequestType:
s.txx.Add(&cp.GetPrepareRequest().(*prepareRequest).minerTx)
}
s.messages <- *cp
}
func (s *service) OnTransaction(tx *transaction.Transaction) {
if s.dbft != nil {
s.transactions <- tx
}
}
// GetPayload returns payload stored in cache.
func (s *service) GetPayload(h util.Uint256) *Payload {
return s.cache.Get(h)
p := s.cache.Get(h)
if p == nil {
return (*Payload)(nil)
}
cp := *p.(*Payload)
return &cp
}
func (s *service) broadcast(p payload.ConsensusPayload) {
switch p.Type() {
case payload.PrepareRequestType:
pr := p.GetPrepareRequest().(*prepareRequest)
pr.minerTx = *s.txx.Get(pr.transactionHashes[0]).(*transaction.Transaction)
}
s.cache.Add(p)
s.Config.Broadcast(p.(*Payload))
}
func (s *service) getTx(h util.Uint256) block.Transaction {
if tx := s.txx.Get(h); tx != nil {
return tx.(*transaction.Transaction)
}
tx, _, _ := s.Config.Chain.GetTransaction(h)
return tx
}
func (s *service) verifyBlock(b block.Block) bool {
coreb := &b.(*neoBlock).Block
for _, tx := range coreb.Transactions {
if err := s.Chain.VerifyTx(tx, coreb); err != nil {
return false
}
}
return true
}
func (s *service) processBlock(b block.Block) {
bb := &b.(*neoBlock).Block
bb.Script = s.getBlockWitness(bb)
if err := s.Chain.AddBlock(bb); err != nil {
s.log.Warnf("error on add block: %v", err)
}
}
func (s *service) getBlockWitness(b *core.Block) *transaction.Witness {
dctx := s.dbft.Context
pubs := convertKeys(dctx.Validators)
sigs := make(map[*keys.PublicKey][]byte)
for i := range pubs {
if p := dctx.CommitPayloads[i]; p != nil && p.ViewNumber() == dctx.ViewNumber {
sigs[pubs[i]] = p.GetCommit().Signature()
}
}
m := s.dbft.Context.M()
verif, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
if err != nil {
s.log.Warnf("can't create multisig redeem script: %v", err)
return nil
}
sort.Sort(keys.PublicKeys(pubs))
var invoc []byte
for i, j := 0, 0; i < len(pubs) && j < m; i++ {
if sig, ok := sigs[pubs[i]]; ok {
invoc = append(invoc, byte(vm.PUSHBYTES64))
invoc = append(invoc, sig...)
j++
}
}
return &transaction.Witness{
InvocationScript: invoc,
VerificationScript: verif,
}
}
func (s *service) getBlock(h util.Uint256) block.Block {
b, err := s.Chain.GetBlock(h)
if err != nil {
return nil
}
return &neoBlock{Block: *b}
}
func (s *service) getVerifiedTx(count int) []block.Transaction {
pool := s.Config.Chain.GetMemPool()
txx := pool.GetVerifiedTransactions()
res := make([]block.Transaction, len(txx)+1)
for i := 1; i < len(res); i++ {
res[i] = txx[i]
}
for {
nonce := rand.Uint32()
res[0] = &transaction.Transaction{
Type: transaction.MinerType,
Version: 0,
Data: &transaction.MinerTX{Nonce: nonce},
Attributes: nil,
Inputs: nil,
Outputs: nil,
Scripts: nil,
Trimmed: false,
}
if tx, _, _ := s.Chain.GetTransaction(res[0].Hash()); tx == nil {
break
}
}
s.txx.Add(res[0])
return res
}
func (s *service) getValidators(txx ...block.Transaction) []crypto.PublicKey {
var pKeys []*keys.PublicKey
if len(txx) == 0 {
pKeys, _ = s.Chain.GetValidators()
} else {
ntxx := make([]*transaction.Transaction, len(txx))
for i := range ntxx {
ntxx[i] = txx[i].(*transaction.Transaction)
}
pKeys, _ = s.Chain.GetValidators(ntxx...)
}
pubs := make([]crypto.PublicKey, len(pKeys))
for i := range pKeys {
pubs[i] = &publicKey{PublicKey: pKeys[i]}
}
return pubs
}
func (s *service) getConsensusAddress(validators ...crypto.PublicKey) (h util.Uint160) {
pubs := convertKeys(validators)
script, err := smartcontract.CreateMultiSigRedeemScript(s.dbft.M(), pubs)
if err != nil {
return
}
return crypto.Hash160(script)
}
func convertKeys(validators []crypto.PublicKey) (pubs []*keys.PublicKey) {
pubs = make([]*keys.PublicKey, len(validators))
for i, k := range validators {
pubs[i] = k.(*publicKey).PublicKey
}
return
}

51
pkg/consensus/crypto.go Normal file
View file

@ -0,0 +1,51 @@
package consensus
import (
"crypto/sha256"
"errors"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
)
// privateKey is a wrapper around keys.PrivateKey
// which implements crypto.PrivateKey interface.
type privateKey struct {
*keys.PrivateKey
}
// MarshalBinary implements encoding.BinaryMarshaler interface.
func (p privateKey) MarshalBinary() (data []byte, err error) {
return p.PrivateKey.Bytes(), nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface.
func (p *privateKey) UnmarshalBinary(data []byte) (err error) {
p.PrivateKey, err = keys.NewPrivateKeyFromBytes(data)
return
}
// publicKey is a wrapper around keys.PublicKey
// which implements crypto.PublicKey interface.
type publicKey struct {
*keys.PublicKey
}
// MarshalBinary implements encoding.BinaryMarshaler interface.
func (p publicKey) MarshalBinary() (data []byte, err error) {
return p.PublicKey.Bytes(), nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface.
func (p *publicKey) UnmarshalBinary(data []byte) error {
return p.PublicKey.DecodeBytes(data)
}
// Verify implements crypto.PublicKey interface.
func (p publicKey) Verify(msg, sig []byte) error {
hash := sha256.Sum256(msg)
if p.PublicKey.Verify(sig, hash[:]) {
return nil
}
return errors.New("error")
}

View file

@ -0,0 +1,43 @@
package consensus
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require"
)
func TestCrypt(t *testing.T) {
key, err := keys.NewPrivateKey()
require.NoError(t, err)
priv := privateKey{key}
data, err := priv.MarshalBinary()
require.NoError(t, err)
key1, err := keys.NewPrivateKey()
require.NoError(t, err)
priv1 := privateKey{key1}
require.NotEqual(t, priv, priv1)
require.NoError(t, priv1.UnmarshalBinary(data))
require.Equal(t, priv, priv1)
pub := publicKey{key.PublicKey()}
data, err = pub.MarshalBinary()
require.NoError(t, err)
pub1 := publicKey{key1.PublicKey()}
require.NotEqual(t, pub, pub1)
require.NoError(t, pub1.UnmarshalBinary(data))
require.Equal(t, pub, pub1)
data = []byte{1, 2, 3, 4}
sign, err := priv.Sign(data)
require.NoError(t, err)
require.NoError(t, pub.Verify(data, sign))
sign[0] = ^sign[0]
require.Error(t, pub.Verify(data, sign))
}

19
pkg/consensus/logger.go Normal file
View file

@ -0,0 +1,19 @@
package consensus
import (
"go.uber.org/zap"
)
func getLogger() (*zap.Logger, error) {
cc := zap.NewDevelopmentConfig()
cc.DisableCaller = true
cc.DisableStacktrace = true
cc.Encoding = "console"
log, err := cc.Build()
if err != nil {
return nil, err
}
return log.With(zap.String("module", "dbft")), nil
}

View file

@ -1,12 +1,16 @@
package consensus
import (
"crypto/sha256"
"fmt"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/smartcontract"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
"github.com/nspcc-dev/dbft/payload"
"github.com/pkg/errors"
)
@ -24,11 +28,11 @@ type (
Payload struct {
message
Version uint32
ValidatorIndex uint16
PrevHash util.Uint256
Height uint32
Timestamp uint32
version uint32
validatorIndex uint16
prevHash util.Uint256
height uint32
timestamp uint32
Witness transaction.Witness
}
@ -43,13 +47,125 @@ const (
recoveryMessageType messageType = 0x41
)
// ViewNumber implements payload.ConsensusPayload interface.
func (p Payload) ViewNumber() byte {
return p.message.ViewNumber
}
// SetViewNumber implements payload.ConsensusPayload interface.
func (p *Payload) SetViewNumber(view byte) {
p.message.ViewNumber = view
}
// Type implements payload.ConsensusPayload interface.
func (p Payload) Type() payload.MessageType {
return payload.MessageType(p.message.Type)
}
// SetType implements payload.ConsensusPayload interface.
func (p *Payload) SetType(t payload.MessageType) {
p.message.Type = messageType(t)
}
// Payload implements payload.ConsensusPayload interface.
func (p Payload) Payload() interface{} {
return p.payload
}
// SetPayload implements payload.ConsensusPayload interface.
func (p *Payload) SetPayload(pl interface{}) {
p.payload = pl.(io.Serializable)
}
// GetChangeView implements payload.ConsensusPayload interface.
func (p Payload) GetChangeView() payload.ChangeView { return p.payload.(payload.ChangeView) }
// GetPrepareRequest implements payload.ConsensusPayload interface.
func (p Payload) GetPrepareRequest() payload.PrepareRequest {
return p.payload.(payload.PrepareRequest)
}
// GetPrepareResponse implements payload.ConsensusPayload interface.
func (p Payload) GetPrepareResponse() payload.PrepareResponse {
return p.payload.(payload.PrepareResponse)
}
// GetCommit implements payload.ConsensusPayload interface.
func (p Payload) GetCommit() payload.Commit { return p.payload.(payload.Commit) }
// GetRecoveryRequest implements payload.ConsensusPayload interface.
func (p Payload) GetRecoveryRequest() payload.RecoveryRequest {
return p.payload.(payload.RecoveryRequest)
}
// GetRecoveryMessage implements payload.ConsensusPayload interface.
func (p Payload) GetRecoveryMessage() payload.RecoveryMessage {
return p.payload.(payload.RecoveryMessage)
}
// MarshalUnsigned implements payload.ConsensusPayload interface.
func (p Payload) MarshalUnsigned() []byte {
w := io.NewBufBinWriter()
p.EncodeBinaryUnsigned(w.BinWriter)
return w.Bytes()
}
// UnmarshalUnsigned implements payload.ConsensusPayload interface.
func (p *Payload) UnmarshalUnsigned(data []byte) error {
r := io.NewBinReaderFromBuf(data)
p.DecodeBinaryUnsigned(r)
return r.Err
}
// Version implements payload.ConsensusPayload interface.
func (p Payload) Version() uint32 {
return p.version
}
// SetVersion implements payload.ConsensusPayload interface.
func (p *Payload) SetVersion(v uint32) {
p.version = v
}
// ValidatorIndex implements payload.ConsensusPayload interface.
func (p Payload) ValidatorIndex() uint16 {
return p.validatorIndex
}
// SetValidatorIndex implements payload.ConsensusPayload interface.
func (p *Payload) SetValidatorIndex(i uint16) {
p.validatorIndex = i
}
// PrevHash implements payload.ConsensusPayload interface.
func (p Payload) PrevHash() util.Uint256 {
return p.prevHash
}
// SetPrevHash implements payload.ConsensusPayload interface.
func (p *Payload) SetPrevHash(h util.Uint256) {
p.prevHash = h
}
// Height implements payload.ConsensusPayload interface.
func (p Payload) Height() uint32 {
return p.height
}
// SetHeight implements payload.ConsensusPayload interface.
func (p *Payload) SetHeight(h uint32) {
p.height = h
}
// EncodeBinaryUnsigned writes payload to w excluding signature.
func (p *Payload) EncodeBinaryUnsigned(w *io.BinWriter) {
w.WriteLE(p.Version)
w.WriteBE(p.PrevHash[:])
w.WriteLE(p.Height)
w.WriteLE(p.ValidatorIndex)
w.WriteLE(p.Timestamp)
func (p Payload) EncodeBinaryUnsigned(w *io.BinWriter) {
w.WriteLE(p.version)
w.WriteBE(p.prevHash[:])
w.WriteLE(p.height)
w.WriteLE(p.validatorIndex)
w.WriteLE(p.timestamp)
ww := io.NewBufBinWriter()
p.message.EncodeBinary(ww.BinWriter)
@ -64,20 +180,59 @@ func (p *Payload) EncodeBinary(w *io.BinWriter) {
p.Witness.EncodeBinary(w)
}
// DecodeBinaryUnsigned reads payload from w excluding signature.
func (p *Payload) DecodeBinaryUnsigned(r *io.BinReader) {
r.ReadLE(&p.Version)
r.ReadBE(p.PrevHash[:])
r.ReadLE(&p.Height)
r.ReadLE(&p.ValidatorIndex)
r.ReadLE(&p.Timestamp)
// Sign signs payload using the private key.
// It also sets corresponding verification and invocation scripts.
func (p *Payload) Sign(key *privateKey) error {
sig, err := key.Sign(p.MarshalUnsigned())
if err != nil {
return err
}
data := r.ReadBytes()
rr := io.NewBinReaderFromBuf(data)
p.message.DecodeBinary(rr)
verif, err := smartcontract.CreateSignatureRedeemScript(key.PublicKey())
if err != nil {
return err
}
p.Witness.InvocationScript = append([]byte{byte(vm.PUSHBYTES64)}, sig...)
p.Witness.VerificationScript = verif
return nil
}
// Hash returns 32-byte message hash.
// Verify verifies payload using provided Witness.
func (p *Payload) Verify() bool {
h := sha256.Sum256(p.MarshalUnsigned())
v := vm.New()
v.SetCheckedHash(h[:])
v.Load(append(p.Witness.InvocationScript, p.Witness.VerificationScript...))
if err := v.Run(); err != nil || v.Estack().Len() == 0 {
return false
}
result, err := v.Estack().Top().TryBool()
return err == nil && result
}
// DecodeBinaryUnsigned reads payload from w excluding signature.
func (p *Payload) DecodeBinaryUnsigned(r *io.BinReader) {
r.ReadLE(&p.version)
r.ReadBE(p.prevHash[:])
r.ReadLE(&p.height)
r.ReadLE(&p.validatorIndex)
r.ReadLE(&p.timestamp)
data := r.ReadBytes()
if r.Err != nil {
return
}
rr := io.NewBinReaderFromBuf(data)
p.message.DecodeBinary(rr)
r.Err = rr.Err
}
// Hash implements payload.ConsensusPayload interface.
func (p *Payload) Hash() util.Uint256 {
w := io.NewBufBinWriter()
p.EncodeBinaryUnsigned(w.BinWriter)
@ -88,6 +243,9 @@ func (p *Payload) Hash() util.Uint256 {
// DecodeBinary implements io.Serializable interface.
func (p *Payload) DecodeBinary(r *io.BinReader) {
p.DecodeBinaryUnsigned(r)
if r.Err != nil {
return
}
var b byte
r.ReadLE(&b)
@ -114,8 +272,8 @@ func (m *message) DecodeBinary(r *io.BinReader) {
switch m.Type {
case changeViewType:
cv := new(changeView)
// NewViewNumber is not marshaled
cv.NewViewNumber = m.ViewNumber + 1
// newViewNumber is not marshaled
cv.newViewNumber = m.ViewNumber + 1
m.payload = cv
case prepareRequestType:
m.payload = new(prepareRequest)

View file

@ -8,8 +8,11 @@ import (
"time"
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/dbft/payload"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -22,6 +25,53 @@ var messageTypes = []messageType{
recoveryMessageType,
}
func TestConsensusPayload_Setters(t *testing.T) {
var p Payload
p.SetVersion(1)
assert.EqualValues(t, 1, p.Version())
p.SetPrevHash(util.Uint256{1, 2, 3})
assert.Equal(t, util.Uint256{1, 2, 3}, p.PrevHash())
p.SetValidatorIndex(4)
assert.EqualValues(t, 4, p.ValidatorIndex())
p.SetHeight(11)
assert.EqualValues(t, 11, p.Height())
p.SetViewNumber(2)
assert.EqualValues(t, 2, p.ViewNumber())
p.SetType(payload.PrepareRequestType)
assert.Equal(t, payload.PrepareRequestType, p.Type())
pl := randomMessage(t, prepareRequestType)
p.SetPayload(pl)
require.Equal(t, pl, p.Payload())
require.Equal(t, pl, p.GetPrepareRequest())
pl = randomMessage(t, prepareResponseType)
p.SetPayload(pl)
require.Equal(t, pl, p.GetPrepareResponse())
pl = randomMessage(t, commitType)
p.SetPayload(pl)
require.Equal(t, pl, p.GetCommit())
pl = randomMessage(t, changeViewType)
p.SetPayload(pl)
require.Equal(t, pl, p.GetChangeView())
pl = randomMessage(t, recoveryRequestType)
p.SetPayload(pl)
require.Equal(t, pl, p.GetRecoveryRequest())
pl = randomMessage(t, recoveryMessageType)
p.SetPayload(pl)
require.Equal(t, pl, p.GetRecoveryMessage())
}
func TestConsensusPayload_Hash(t *testing.T) {
dataHex := "00000000d8fb8d3b143b5f98468ef701909c976505a110a01e26c5e99be9a90cff979199b6fc33000400000000008d2000184dc95de24018f9ad71f4448a2b438eaca8b4b2ab6b4524b5a69a45d920c35103f3901444320656c390ff39c0062f5e8e138ce446a40c7e4ba1af1f8247ebbdf49295933715d3a67949714ff924f8a28cec5b954c71eca3bfaf0e9d4b1f87b4e21e9ba4ae18f97de71501b5c5d07edc200bd66a46b9b28b1a371f2195c10b0af90000e24018f900000000014140c9faaee59942f58da0e5268bc199632f2a3ad0fcbee68681a4437f140b49512e8d9efc6880eb44d3490782895a5794f35eeccee2923ce0c76fa7a1890f934eac232103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1ac"
data, err := hex.DecodeString(dataHex)
@ -40,9 +90,76 @@ func TestConsensusPayload_Serializable(t *testing.T) {
for _, mt := range messageTypes {
p := randomPayload(t, mt)
testSerializable(t, p, new(Payload))
data := p.MarshalUnsigned()
pu := new(Payload)
require.NoError(t, pu.UnmarshalUnsigned(data))
p.Witness = transaction.Witness{}
require.Equal(t, p, pu)
}
}
func TestConsensusPayload_DecodeBinaryInvalid(t *testing.T) {
// PrepareResponse ConsensusPayload consists of:
// 46-byte common prefix
// 1-byte varint length of the payload (34),
// - 1-byte view number
// - 1-byte message type (PrepareResponse)
// - 32-byte preparation hash
// 1-byte delimiter (1)
// 2-byte for empty invocation and verification scripts
const (
lenIndex = 46
typeIndex = 47
delimeterIndex = 81
)
buf := make([]byte, 46+1+34+1+2)
expected := &Payload{
message: message{
Type: prepareResponseType,
payload: &prepareResponse{},
},
Witness: transaction.Witness{
InvocationScript: []byte{},
VerificationScript: []byte{},
},
}
// valid payload
buf[delimeterIndex] = 1
buf[lenIndex] = 34
buf[typeIndex] = byte(prepareResponseType)
r := io.NewBinReaderFromBuf(buf)
p := new(Payload)
p.DecodeBinary(r)
require.NoError(t, r.Err)
require.Equal(t, expected, p)
// invalid type
buf[typeIndex] = 0xFF
r = io.NewBinReaderFromBuf(buf)
new(Payload).DecodeBinary(r)
require.Error(t, r.Err)
// invalid format
buf[delimeterIndex] = 0
buf[typeIndex] = byte(prepareResponseType)
r = io.NewBinReaderFromBuf(buf)
new(Payload).DecodeBinary(r)
require.Error(t, r.Err)
// invalid message length
buf[delimeterIndex] = 1
buf[lenIndex] = 0xFF
buf[typeIndex] = byte(prepareResponseType)
r = io.NewBinReaderFromBuf(buf)
new(Payload).DecodeBinary(r)
require.Error(t, r.Err)
}
func TestCommit_Serializable(t *testing.T) {
c := randomMessage(t, commitType)
testSerializable(t, c, new(commit))
@ -75,19 +192,19 @@ func randomPayload(t *testing.T, mt messageType) *Payload {
ViewNumber: byte(rand.Uint32()),
payload: randomMessage(t, mt),
},
Version: 1,
ValidatorIndex: 13,
Height: rand.Uint32(),
Timestamp: rand.Uint32(),
version: 1,
validatorIndex: 13,
height: rand.Uint32(),
timestamp: rand.Uint32(),
Witness: transaction.Witness{
InvocationScript: fillRandom(t, make([]byte, 3)),
VerificationScript: fillRandom(t, make([]byte, 4)),
},
}
fillRandom(t, p.PrevHash[:])
fillRandom(t, p.prevHash[:])
if mt == changeViewType {
p.payload.(*changeView).NewViewNumber = p.ViewNumber + 1
p.payload.(*changeView).newViewNumber = p.ViewNumber() + 1
}
return p
@ -97,20 +214,20 @@ func randomMessage(t *testing.T, mt messageType) io.Serializable {
switch mt {
case changeViewType:
return &changeView{
Timestamp: rand.Uint32(),
timestamp: rand.Uint32(),
}
case prepareRequestType:
return randomPrepareRequest(t)
case prepareResponseType:
resp := &prepareResponse{}
fillRandom(t, resp.PreparationHash[:])
fillRandom(t, resp.preparationHash[:])
return resp
case commitType:
var c commit
fillRandom(t, c.Signature[:])
fillRandom(t, c.signature[:])
return &c
case recoveryRequestType:
return &recoveryRequest{Timestamp: rand.Uint32()}
return &recoveryRequest{timestamp: rand.Uint32()}
case recoveryMessageType:
return randomRecoveryMessage(t)
default:
@ -123,17 +240,17 @@ func randomPrepareRequest(t *testing.T) *prepareRequest {
const txCount = 3
req := &prepareRequest{
Timestamp: rand.Uint32(),
Nonce: rand.Uint64(),
TransactionHashes: make([]util.Uint256, txCount),
MinerTransaction: *newMinerTx(rand.Uint32()),
timestamp: rand.Uint32(),
nonce: rand.Uint64(),
transactionHashes: make([]util.Uint256, txCount),
minerTx: *newMinerTx(rand.Uint32()),
}
req.TransactionHashes[0] = req.MinerTransaction.Hash()
req.transactionHashes[0] = req.minerTx.Hash()
for i := 1; i < txCount; i++ {
fillRandom(t, req.TransactionHashes[i][:])
fillRandom(t, req.transactionHashes[i][:])
}
fillRandom(t, req.NextConsensus[:])
fillRandom(t, req.nextConsensus[:])
return req
}
@ -144,27 +261,27 @@ func randomRecoveryMessage(t *testing.T) *recoveryMessage {
prepReq := result.(*prepareRequest)
return &recoveryMessage{
PreparationPayloads: []*preparationCompact{
preparationPayloads: []*preparationCompact{
{
ValidatorIndex: 1,
InvocationScript: fillRandom(t, make([]byte, 10)),
},
},
CommitPayloads: []*commitCompact{
commitPayloads: []*commitCompact{
{
ViewNumber: 0,
ValidatorIndex: 1,
Signature: fillRandom(t, make([]byte, signatureSize)),
Signature: [64]byte{1, 2, 3},
InvocationScript: fillRandom(t, make([]byte, 20)),
},
{
ViewNumber: 0,
ValidatorIndex: 2,
Signature: fillRandom(t, make([]byte, signatureSize)),
Signature: [64]byte{11, 3, 4, 98},
InvocationScript: fillRandom(t, make([]byte, 10)),
},
},
ChangeViewPayloads: []*changeViewCompact{
changeViewPayloads: []*changeViewCompact{
{
Timestamp: rand.Uint32(),
ValidatorIndex: 3,
@ -172,10 +289,21 @@ func randomRecoveryMessage(t *testing.T) *recoveryMessage {
InvocationScript: fillRandom(t, make([]byte, 4)),
},
},
PrepareRequest: prepReq,
prepareRequest: prepReq,
}
}
func TestPayload_Sign(t *testing.T) {
key, err := keys.NewPrivateKey()
require.NoError(t, err)
priv := &privateKey{key}
p := randomPayload(t, prepareRequestType)
require.False(t, p.Verify())
require.NoError(t, p.Sign(priv))
require.True(t, p.Verify())
}
func TestMessageType_String(t *testing.T) {
require.Equal(t, "ChangeView", changeViewType.String())
require.Equal(t, "PrepareRequest", prepareRequestType.String())

View file

@ -4,31 +4,58 @@ import (
"github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/dbft/payload"
)
// prepareRequest represents dBFT PrepareRequest message.
// prepareRequest represents dBFT prepareRequest message.
type prepareRequest struct {
Timestamp uint32
Nonce uint64
TransactionHashes []util.Uint256
MinerTransaction transaction.Transaction
NextConsensus util.Uint160
timestamp uint32
nonce uint64
transactionHashes []util.Uint256
minerTx transaction.Transaction
nextConsensus util.Uint160
}
var _ payload.PrepareRequest = (*prepareRequest)(nil)
// EncodeBinary implements io.Serializable interface.
func (p *prepareRequest) EncodeBinary(w *io.BinWriter) {
w.WriteLE(p.Timestamp)
w.WriteLE(p.Nonce)
w.WriteBE(p.NextConsensus[:])
w.WriteArray(p.TransactionHashes)
p.MinerTransaction.EncodeBinary(w)
w.WriteLE(p.timestamp)
w.WriteLE(p.nonce)
w.WriteBE(p.nextConsensus[:])
w.WriteArray(p.transactionHashes)
p.minerTx.EncodeBinary(w)
}
// DecodeBinary implements io.Serializable interface.
func (p *prepareRequest) DecodeBinary(r *io.BinReader) {
r.ReadLE(&p.Timestamp)
r.ReadLE(&p.Nonce)
r.ReadBE(p.NextConsensus[:])
r.ReadArray(&p.TransactionHashes)
p.MinerTransaction.DecodeBinary(r)
r.ReadLE(&p.timestamp)
r.ReadLE(&p.nonce)
r.ReadBE(p.nextConsensus[:])
r.ReadArray(&p.transactionHashes)
p.minerTx.DecodeBinary(r)
}
// Timestamp implements payload.PrepareRequest interface.
func (p *prepareRequest) Timestamp() uint32 { return p.timestamp }
// SetTimestamp implements payload.PrepareRequest interface.
func (p *prepareRequest) SetTimestamp(ts uint32) { p.timestamp = ts }
// Nonce implements payload.PrepareRequest interface.
func (p *prepareRequest) Nonce() uint64 { return p.nonce }
// SetNonce implements payload.PrepareRequest interface.
func (p *prepareRequest) SetNonce(nonce uint64) { p.nonce = nonce }
// TransactionHashes implements payload.PrepareRequest interface.
func (p *prepareRequest) TransactionHashes() []util.Uint256 { return p.transactionHashes }
// SetTransactionHashes implements payload.PrepareRequest interface.
func (p *prepareRequest) SetTransactionHashes(hs []util.Uint256) { p.transactionHashes = hs }
// NextConsensus implements payload.PrepareRequest interface.
func (p *prepareRequest) NextConsensus() util.Uint160 { return p.nextConsensus }
// SetNextConsensus implements payload.PrepareRequest interface.
func (p *prepareRequest) SetNextConsensus(nc util.Uint160) { p.nextConsensus = nc }

View file

@ -0,0 +1,28 @@
package consensus
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestPrepareRequest_Setters(t *testing.T) {
var p prepareRequest
p.SetTimestamp(123)
require.EqualValues(t, 123, p.Timestamp())
p.SetNextConsensus(util.Uint160{5, 6, 7})
require.Equal(t, util.Uint160{5, 6, 7}, p.NextConsensus())
p.SetNonce(8765)
require.EqualValues(t, 8765, p.Nonce())
var hashes [2]util.Uint256
fillRandom(t, hashes[0][:])
fillRandom(t, hashes[1][:])
p.SetTransactionHashes(hashes[:])
require.Equal(t, hashes[:], p.TransactionHashes())
}

View file

@ -3,19 +3,28 @@ package consensus
import (
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/dbft/payload"
)
// prepareResponse represents dBFT PrepareResponse message.
type prepareResponse struct {
PreparationHash util.Uint256
preparationHash util.Uint256
}
var _ payload.PrepareResponse = (*prepareResponse)(nil)
// EncodeBinary implements io.Serializable interface.
func (p *prepareResponse) EncodeBinary(w *io.BinWriter) {
w.WriteBE(p.PreparationHash[:])
w.WriteBE(p.preparationHash[:])
}
// DecodeBinary implements io.Serializable interface.
func (p *prepareResponse) DecodeBinary(r *io.BinReader) {
r.ReadBE(p.PreparationHash[:])
r.ReadBE(p.preparationHash[:])
}
// PreparationHash implements payload.PrepareResponse interface.
func (p *prepareResponse) PreparationHash() util.Uint256 { return p.preparationHash }
// SetPreparationHash implements payload.PrepareResponse interface.
func (p *prepareResponse) SetPreparationHash(h util.Uint256) { p.preparationHash = h }

View file

@ -0,0 +1,15 @@
package consensus
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestPrepareResponse_Setters(t *testing.T) {
var p prepareResponse
p.SetPreparationHash(util.Uint256{1, 2, 3})
require.Equal(t, util.Uint256{1, 2, 3}, p.PreparationHash())
}

View file

@ -3,17 +3,18 @@ package consensus
import (
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util"
"github.com/nspcc-dev/dbft/payload"
"github.com/pkg/errors"
)
type (
// recoveryMessage represents dBFT Recovery message.
recoveryMessage struct {
PreparationHash *util.Uint256
PreparationPayloads []*preparationCompact
CommitPayloads []*commitCompact
ChangeViewPayloads []*changeViewCompact
PrepareRequest *prepareRequest
preparationHash *util.Uint256
preparationPayloads []*preparationCompact
commitPayloads []*commitCompact
changeViewPayloads []*changeViewCompact
prepareRequest *prepareRequest
}
changeViewCompact struct {
@ -26,7 +27,7 @@ type (
commitCompact struct {
ViewNumber byte
ValidatorIndex uint16
Signature []byte
Signature [signatureSize]byte
InvocationScript []byte
}
@ -36,53 +37,54 @@ type (
}
)
const uint256size = 32
var _ payload.RecoveryMessage = (*recoveryMessage)(nil)
// DecodeBinary implements io.Serializable interface.
func (m *recoveryMessage) DecodeBinary(r *io.BinReader) {
r.ReadArray(&m.ChangeViewPayloads)
r.ReadArray(&m.changeViewPayloads)
var hasReq bool
r.ReadLE(&hasReq)
if hasReq {
m.PrepareRequest = new(prepareRequest)
m.PrepareRequest.DecodeBinary(r)
m.prepareRequest = new(prepareRequest)
m.prepareRequest.DecodeBinary(r)
} else {
l := r.ReadVarUint()
if l != 0 {
if l == uint256size {
m.PreparationHash = new(util.Uint256)
r.ReadBE(m.PreparationHash[:])
if l == util.Uint256Size {
m.preparationHash = new(util.Uint256)
r.ReadBE(m.preparationHash[:])
} else {
r.Err = errors.New("invalid data")
}
} else {
m.PreparationHash = nil
m.preparationHash = nil
}
}
r.ReadArray(&m.PreparationPayloads)
r.ReadArray(&m.CommitPayloads)
r.ReadArray(&m.preparationPayloads)
r.ReadArray(&m.commitPayloads)
}
// EncodeBinary implements io.Serializable interface.
func (m *recoveryMessage) EncodeBinary(w *io.BinWriter) {
w.WriteArray(m.ChangeViewPayloads)
w.WriteArray(m.changeViewPayloads)
hasReq := m.PrepareRequest != nil
hasReq := m.prepareRequest != nil
w.WriteLE(hasReq)
if hasReq {
m.PrepareRequest.EncodeBinary(w)
m.prepareRequest.EncodeBinary(w)
} else {
if m.PreparationHash == nil {
if m.preparationHash == nil {
w.WriteVarUint(0)
} else {
w.WriteVarUint(uint256size)
w.WriteBE(m.PreparationHash[:])
w.WriteVarUint(util.Uint256Size)
w.WriteBE(m.preparationHash[:])
}
}
w.WriteArray(m.PreparationPayloads)
w.WriteArray(m.CommitPayloads)
w.WriteArray(m.preparationPayloads)
w.WriteArray(m.commitPayloads)
}
// DecodeBinary implements io.Serializable interface.
@ -105,9 +107,7 @@ func (p *changeViewCompact) EncodeBinary(w *io.BinWriter) {
func (p *commitCompact) DecodeBinary(r *io.BinReader) {
r.ReadLE(&p.ViewNumber)
r.ReadLE(&p.ValidatorIndex)
p.Signature = make([]byte, signatureSize)
r.ReadBE(&p.Signature)
r.ReadBE(p.Signature[:])
p.InvocationScript = r.ReadBytes()
}
@ -130,3 +130,108 @@ func (p *preparationCompact) EncodeBinary(w *io.BinWriter) {
w.WriteLE(p.ValidatorIndex)
w.WriteBytes(p.InvocationScript)
}
// AddPayload implements payload.RecoveryMessage interface.
func (m *recoveryMessage) AddPayload(p payload.ConsensusPayload) {
switch p.Type() {
case payload.PrepareRequestType:
m.prepareRequest = p.GetPrepareRequest().(*prepareRequest)
case payload.PrepareResponseType:
m.preparationPayloads = append(m.preparationPayloads, &preparationCompact{
ValidatorIndex: p.ValidatorIndex(),
InvocationScript: p.(*Payload).Witness.InvocationScript,
})
case payload.ChangeViewType:
m.changeViewPayloads = append(m.changeViewPayloads, &changeViewCompact{
ValidatorIndex: p.ValidatorIndex(),
OriginalViewNumber: p.ViewNumber(),
Timestamp: p.GetChangeView().Timestamp(),
InvocationScript: p.(*Payload).Witness.InvocationScript,
})
case payload.CommitType:
m.commitPayloads = append(m.commitPayloads, &commitCompact{
ValidatorIndex: p.ValidatorIndex(),
ViewNumber: p.ViewNumber(),
Signature: p.GetCommit().(*commit).signature,
InvocationScript: p.(*Payload).Witness.InvocationScript,
})
}
}
// GetPrepareRequest implements payload.RecoveryMessage interface.
func (m *recoveryMessage) GetPrepareRequest(p payload.ConsensusPayload) payload.ConsensusPayload {
if m.prepareRequest == nil {
return nil
}
return fromPayload(prepareRequestType, p.(*Payload), m.prepareRequest)
}
// GetPrepareResponses implements payload.RecoveryMessage interface.
func (m *recoveryMessage) GetPrepareResponses(p payload.ConsensusPayload) []payload.ConsensusPayload {
if m.preparationHash == nil {
return nil
}
ps := make([]payload.ConsensusPayload, len(m.preparationPayloads))
for i, resp := range m.preparationPayloads {
ps[i] = fromPayload(prepareResponseType, p.(*Payload), &prepareResponse{
preparationHash: *m.preparationHash,
})
ps[i].SetValidatorIndex(resp.ValidatorIndex)
}
return ps
}
// GetChangeViews implements payload.RecoveryMessage interface.
func (m *recoveryMessage) GetChangeViews(p payload.ConsensusPayload) []payload.ConsensusPayload {
ps := make([]payload.ConsensusPayload, len(m.changeViewPayloads))
for i, cv := range m.changeViewPayloads {
ps[i] = fromPayload(changeViewType, p.(*Payload), &changeView{
newViewNumber: cv.OriginalViewNumber + 1,
timestamp: cv.Timestamp,
})
ps[i].SetValidatorIndex(cv.ValidatorIndex)
}
return ps
}
// GetCommits implements payload.RecoveryMessage interface.
func (m *recoveryMessage) GetCommits(p payload.ConsensusPayload) []payload.ConsensusPayload {
ps := make([]payload.ConsensusPayload, len(m.commitPayloads))
for i, c := range m.commitPayloads {
cc := commit{signature: c.Signature}
ps[i] = fromPayload(commitType, p.(*Payload), &cc)
ps[i].SetValidatorIndex(c.ValidatorIndex)
}
return ps
}
// PreparationHash implements payload.RecoveryMessage interface.
func (m *recoveryMessage) PreparationHash() *util.Uint256 {
return m.preparationHash
}
// SetPreparationHash implements payload.RecoveryMessage interface.
func (m *recoveryMessage) SetPreparationHash(h *util.Uint256) {
m.preparationHash = h
}
func fromPayload(t messageType, recovery *Payload, p io.Serializable) *Payload {
return &Payload{
message: message{
Type: t,
ViewNumber: recovery.message.ViewNumber,
payload: p,
},
version: recovery.Version(),
prevHash: recovery.PrevHash(),
height: recovery.Height(),
}
}

View file

@ -1,18 +1,29 @@
package consensus
import "github.com/CityOfZion/neo-go/pkg/io"
import (
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/nspcc-dev/dbft/payload"
)
// recoveryRequest represents dBFT RecoveryRequest message.
type recoveryRequest struct {
Timestamp uint32
timestamp uint32
}
var _ payload.RecoveryRequest = (*recoveryRequest)(nil)
// DecodeBinary implements io.Serializable interface.
func (m *recoveryRequest) DecodeBinary(r *io.BinReader) {
r.ReadLE(&m.Timestamp)
r.ReadLE(&m.timestamp)
}
// EncodeBinary implements io.Serializable interface.
func (m *recoveryRequest) EncodeBinary(w *io.BinWriter) {
w.WriteLE(m.Timestamp)
w.WriteLE(m.timestamp)
}
// Timestamp implements payload.RecoveryRequest interface.
func (m *recoveryRequest) Timestamp() uint32 { return m.timestamp }
// SetTimestamp implements payload.RecoveryRequest interface.
func (m *recoveryRequest) SetTimestamp(ts uint32) { m.timestamp = ts }

View file

@ -0,0 +1,14 @@
package consensus
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestRecoveryRequest_Setters(t *testing.T) {
var r recoveryRequest
r.SetTimestamp(123)
require.EqualValues(t, 123, r.Timestamp())
}

View file

@ -91,8 +91,8 @@ func (b *BlockBase) EncodeBinary(bw *io.BinWriter) {
b.Script.EncodeBinary(bw)
}
// getHashableData returns serialized hashable data of the block.
func (b *BlockBase) getHashableData() []byte {
// GetHashableData returns serialized hashable data of the block.
func (b *BlockBase) GetHashableData() []byte {
buf := io.NewBufBinWriter()
// No error can occure while encoding hashable fields.
b.encodeHashableFields(buf.BinWriter)
@ -107,7 +107,7 @@ func (b *BlockBase) getHashableData() []byte {
// Since MerkleRoot already contains the hash value of all transactions,
// the modification of transaction will influence the hash value of the block.
func (b *BlockBase) createHash() {
bb := b.getHashableData()
bb := b.GetHashableData()
b.hash = hash.DoubleSha256(bb)
b.verificationHash = hash.Sha256(bb)
}

View file

@ -79,7 +79,7 @@ func newBlock(index uint32, txs ...*transaction.Transaction) *Block {
if err != nil {
panic(err)
}
b := b.getHashableData()
b := b.GetHashableData()
sig, err := pKey.Sign(b)
if err != nil || len(sig) != 64 {
panic(err)

View file

@ -16,6 +16,7 @@ import (
"github.com/CityOfZion/neo-go/pkg/network/payload"
"github.com/CityOfZion/neo-go/pkg/util"
log "github.com/sirupsen/logrus"
"go.uber.org/atomic"
)
const (
@ -61,6 +62,8 @@ type (
register chan Peer
unregister chan peerDrop
quit chan struct{}
connected *atomic.Bool
}
peerDrop struct {
@ -87,9 +90,21 @@ func NewServer(config ServerConfig, chain core.Blockchainer) *Server {
register: make(chan Peer),
unregister: make(chan peerDrop),
peers: make(map[Peer]bool),
consensus: consensus.NewService(),
connected: atomic.NewBool(false),
}
srv, err := consensus.NewService(consensus.Config{
Broadcast: s.handleNewPayload,
Chain: chain,
RequestTx: s.requestTx,
Wallet: config.Wallet,
})
if err != nil {
return nil
}
s.consensus = srv
if s.MinPeers <= 0 {
log.WithFields(log.Fields{
"MinPeers configured": s.MinPeers,
@ -220,8 +235,12 @@ func (s *Server) run() {
"peerCount": s.PeerCount(),
}).Warn("peer disconnected")
addr := drop.peer.PeerAddr().String()
if drop.reason == errIdenticalID {
s.discovery.RegisterBadAddr(addr)
} else {
s.discovery.UnregisterConnectedAddr(addr)
s.discovery.BackFill(addr)
}
updatePeersConnectedMetric(s.PeerCount())
} else {
// else the peer is already gone, which can happen
@ -233,10 +252,31 @@ func (s *Server) run() {
}
}
func (s *Server) tryStartConsensus() {
if s.Wallet == nil || s.connected.Load() {
return
}
if s.PeerCount() >= s.MinPeers {
log.Info("minimum amount of peers were connected to")
if s.connected.CAS(false, true) {
s.consensus.Start()
}
}
}
// Peers returns the current list of peers connected to
// the server.
func (s *Server) Peers() map[Peer]bool {
return s.peers
s.lock.RLock()
defer s.lock.RUnlock()
peers := make(map[Peer]bool, len(s.peers))
for k, v := range s.peers {
peers[k] = v
}
return peers
}
// PeerCount returns the number of current connected peers.
@ -394,6 +434,13 @@ func (s *Server) handleConsensusCmd(cp *consensus.Payload) error {
return nil
}
// handleTxCmd processes received transaction.
// It never returns an error.
func (s *Server) handleTxCmd(tx *transaction.Transaction) error {
s.consensus.OnTransaction(tx)
return nil
}
// handleAddrCmd will process received addresses.
func (s *Server) handleAddrCmd(p Peer, addrs *payload.AddressList) error {
for _, a := range addrs.Addrs {
@ -485,6 +532,9 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
case CMDConsensus:
cp := msg.Payload.(*consensus.Payload)
return s.handleConsensusCmd(cp)
case CMDTX:
tx := msg.Payload.(*transaction.Transaction)
return s.handleTxCmd(tx)
case CMDVersion, CMDVerack:
return fmt.Errorf("received '%s' after the handshake", msg.CommandType())
}
@ -499,6 +549,8 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
return err
}
go s.startProtocol(peer)
s.tryStartConsensus()
default:
return fmt.Errorf("received '%s' during handshake", msg.CommandType())
}
@ -506,6 +558,28 @@ func (s *Server) handleMessage(peer Peer, msg *Message) error {
return nil
}
func (s *Server) handleNewPayload(p *consensus.Payload) {
s.relayInventory(payload.ConsensusType, p.Hash())
}
func (s *Server) requestTx(hashes ...util.Uint256) {
if len(hashes) == 0 {
return
}
s.relayInventory(payload.TXType, hashes...)
}
func (s *Server) relayInventory(t payload.InventoryType, hashes ...util.Uint256) {
for peer := range s.Peers() {
if !peer.Handshaked() {
continue
}
payload := payload.NewInventory(t, hashes)
s.RelayDirectly(peer, payload)
}
}
// RelayTxn a new transaction to the local node and the connected peers.
// Reference: the method OnRelay in C#: https://github.com/neo-project/neo/blob/master/neo/Network/P2P/LocalNode.cs#L159
func (s *Server) RelayTxn(t *transaction.Transaction) RelayReason {

View file

@ -54,6 +54,9 @@ type (
// Level of the internal logger.
LogLevel log.Level
// Wallet is a wallet configuration.
Wallet *config.WalletConfig
}
)
@ -63,6 +66,11 @@ func NewServerConfig(cfg config.Config) ServerConfig {
appConfig := cfg.ApplicationConfiguration
protoConfig := cfg.ProtocolConfiguration
var wc *config.WalletConfig
if appConfig.UnlockWallet.Path != "" {
wc = &appConfig.UnlockWallet
}
return ServerConfig{
UserAgent: cfg.GenerateUserAgent(),
Address: appConfig.Address,
@ -75,5 +83,6 @@ func NewServerConfig(cfg config.Config) ServerConfig {
MaxPeers: appConfig.MaxPeers,
AttemptConnPeers: appConfig.AttemptConnPeers,
MinPeers: appConfig.MinPeers,
Wallet: wc,
}
}

View file

@ -81,6 +81,11 @@ func (a *Account) Encrypt(passphrase string) error {
return nil
}
// PrivateKey returns private key corresponding to the account.
func (a *Account) PrivateKey() *keys.PrivateKey {
return a.privateKey
}
// NewAccountFromWIF creates a new Account from the given WIF.
func NewAccountFromWIF(wif string) (*Account, error) {
privKey, err := keys.NewPrivateKeyFromWIF(wif)