mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-29 13:41:47 +00:00
Merge pull request #507 from nspcc-dev/dbft
network: plug in dBFT library
This commit is contained in:
commit
2d41450ac9
32 changed files with 1426 additions and 153 deletions
|
@ -69,7 +69,3 @@ services:
|
|||
- 20334:20334
|
||||
- 20344:20344
|
||||
- 20354:20354
|
||||
depends_on:
|
||||
- node_one
|
||||
- node_two
|
||||
- node_three
|
||||
|
|
2
Makefile
2
Makefile
|
@ -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"
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
19
go.mod
|
@ -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
19
go.sum
|
@ -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
101
pkg/consensus/block.go
Normal 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 }
|
48
pkg/consensus/block_test.go
Normal file
48
pkg/consensus/block_test.go
Normal 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())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
17
pkg/consensus/change_view_test.go
Normal file
17
pkg/consensus/change_view_test.go
Normal 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())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
16
pkg/consensus/commit_test.go
Normal file
16
pkg/consensus/commit_test.go
Normal 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())
|
||||
}
|
|
@ -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{
|
||||
cache: newFIFOCache(cacheMaxCapacity),
|
||||
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
51
pkg/consensus/crypto.go
Normal 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")
|
||||
}
|
43
pkg/consensus/crypto_test.go
Normal file
43
pkg/consensus/crypto_test.go
Normal 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
19
pkg/consensus/logger.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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 }
|
||||
|
|
28
pkg/consensus/prepare_request_test.go
Normal file
28
pkg/consensus/prepare_request_test.go
Normal 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())
|
||||
}
|
|
@ -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 }
|
||||
|
|
15
pkg/consensus/prepare_response_test.go
Normal file
15
pkg/consensus/prepare_response_test.go
Normal 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())
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
14
pkg/consensus/recovery_request_test.go
Normal file
14
pkg/consensus/recovery_request_test.go
Normal 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())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
s.discovery.UnregisterConnectedAddr(addr)
|
||||
s.discovery.BackFill(addr)
|
||||
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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue