core: allow to use state root in header

This commit is contained in:
Evgenii Stratonikov 2020-11-17 15:57:50 +03:00
parent 3025b42c65
commit 1869d6d460
37 changed files with 349 additions and 124 deletions

View file

@ -281,7 +281,7 @@ func restoreDB(ctx *cli.Context) error {
default: default:
} }
bytes, err := readBlock(reader) bytes, err := readBlock(reader)
block := block.New(cfg.ProtocolConfiguration.Magic) block := block.New(cfg.ProtocolConfiguration.Magic, cfg.ProtocolConfiguration.StateRootInHeader)
newReader := io.NewBinReaderFromBuf(bytes) newReader := io.NewBinReaderFromBuf(bytes)
block.DecodeBinary(newReader) block.DecodeBinary(newReader)
if err != nil { if err != nil {

View file

@ -105,7 +105,7 @@ func TestAppCall(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
ih := hash.Hash160(inner) ih := hash.Hash160(inner)
ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil, nil, zaptest.NewLogger(t)) ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), nil, nil, nil, zaptest.NewLogger(t))
require.NoError(t, ic.DAO.PutContractState(&state.Contract{ require.NoError(t, ic.DAO.PutContractState(&state.Contract{
Script: inner, Script: inner,
Manifest: *m, Manifest: *m,

View file

@ -24,7 +24,9 @@ type (
SecondsPerBlock int `yaml:"SecondsPerBlock"` SecondsPerBlock int `yaml:"SecondsPerBlock"`
SeedList []string `yaml:"SeedList"` SeedList []string `yaml:"SeedList"`
StandbyCommittee []string `yaml:"StandbyCommittee"` StandbyCommittee []string `yaml:"StandbyCommittee"`
ValidatorsCount int `yaml:"ValidatorsCount"` // StateRooInHeader enables storing state root in block header.
StateRootInHeader bool `yaml:"StateRootInHeader"`
ValidatorsCount int `yaml:"ValidatorsCount"`
// Whether to verify received blocks. // Whether to verify received blocks.
VerifyBlocks bool `yaml:"VerifyBlocks"` VerifyBlocks bool `yaml:"VerifyBlocks"`
// Whether to verify transactions in received blocks. // Whether to verify transactions in received blocks.

View file

@ -2,6 +2,7 @@ package consensus
import ( import (
"errors" "errors"
"fmt"
"sort" "sort"
"time" "time"
@ -73,6 +74,8 @@ type service struct {
lastProposal []util.Uint256 lastProposal []util.Uint256
wallet *wallet.Wallet wallet *wallet.Wallet
network netmode.Magic network netmode.Magic
// stateRootEnabled specifies if state root should be exchanged and checked during consensus.
stateRootEnabled bool
// started is a flag set with Start method that runs an event handling // started is a flag set with Start method that runs an event handling
// goroutine. // goroutine.
started *atomic.Bool started *atomic.Bool
@ -116,12 +119,13 @@ func NewService(cfg Config) (Service, error) {
txx: newFIFOCache(cacheMaxCapacity), txx: newFIFOCache(cacheMaxCapacity),
messages: make(chan Payload, 100), messages: make(chan Payload, 100),
transactions: make(chan *transaction.Transaction, 100), transactions: make(chan *transaction.Transaction, 100),
blockEvents: make(chan *coreb.Block, 1), blockEvents: make(chan *coreb.Block, 1),
network: cfg.Chain.GetConfig().Magic, network: cfg.Chain.GetConfig().Magic,
started: atomic.NewBool(false), stateRootEnabled: cfg.Chain.GetConfig().StateRootInHeader,
quit: make(chan struct{}), started: atomic.NewBool(false),
finished: make(chan struct{}), quit: make(chan struct{}),
finished: make(chan struct{}),
} }
if cfg.Wallet == nil { if cfg.Wallet == nil {
@ -168,12 +172,14 @@ func NewService(cfg Config) (Service, error) {
dbft.WithGetConsensusAddress(srv.getConsensusAddress), dbft.WithGetConsensusAddress(srv.getConsensusAddress),
dbft.WithNewConsensusPayload(srv.newPayload), dbft.WithNewConsensusPayload(srv.newPayload),
dbft.WithNewPrepareRequest(func() payload.PrepareRequest { return new(prepareRequest) }), dbft.WithNewPrepareRequest(srv.newPrepareRequest),
dbft.WithNewPrepareResponse(func() payload.PrepareResponse { return new(prepareResponse) }), dbft.WithNewPrepareResponse(func() payload.PrepareResponse { return new(prepareResponse) }),
dbft.WithNewChangeView(func() payload.ChangeView { return new(changeView) }), dbft.WithNewChangeView(func() payload.ChangeView { return new(changeView) }),
dbft.WithNewCommit(func() payload.Commit { return new(commit) }), dbft.WithNewCommit(func() payload.Commit { return new(commit) }),
dbft.WithNewRecoveryRequest(func() payload.RecoveryRequest { return new(recoveryRequest) }), dbft.WithNewRecoveryRequest(func() payload.RecoveryRequest { return new(recoveryRequest) }),
dbft.WithNewRecoveryMessage(func() payload.RecoveryMessage { return new(recoveryMessage) }), dbft.WithNewRecoveryMessage(func() payload.RecoveryMessage {
return &recoveryMessage{stateRootEnabled: srv.stateRootEnabled}
}),
dbft.WithVerifyPrepareRequest(srv.verifyRequest), dbft.WithVerifyPrepareRequest(srv.verifyRequest),
dbft.WithVerifyPrepareResponse(func(_ payload.ConsensusPayload) error { return nil }), dbft.WithVerifyPrepareResponse(func(_ payload.ConsensusPayload) error { return nil }),
) )
@ -191,15 +197,30 @@ var (
) )
// NewPayload creates new consensus payload for the provided network. // NewPayload creates new consensus payload for the provided network.
func NewPayload(m netmode.Magic) *Payload { func NewPayload(m netmode.Magic, stateRootEnabled bool) *Payload {
return &Payload{ return &Payload{
network: m, network: m,
message: new(message), message: &message{
stateRootEnabled: stateRootEnabled,
},
} }
} }
func (s *service) newPayload() payload.ConsensusPayload { func (s *service) newPayload() payload.ConsensusPayload {
return NewPayload(s.network) return NewPayload(s.network, s.stateRootEnabled)
}
func (s *service) newPrepareRequest() payload.PrepareRequest {
r := new(prepareRequest)
if s.stateRootEnabled {
r.stateRootEnabled = true
if sr, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1); err == nil {
r.stateRoot = sr.Root
} else {
panic(err)
}
}
return r
} }
func (s *service) Start() { func (s *service) Start() {
@ -446,6 +467,14 @@ func (s *service) verifyBlock(b block.Block) bool {
func (s *service) verifyRequest(p payload.ConsensusPayload) error { func (s *service) verifyRequest(p payload.ConsensusPayload) error {
req := p.GetPrepareRequest().(*prepareRequest) req := p.GetPrepareRequest().(*prepareRequest)
if s.stateRootEnabled {
sr, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1)
if err != nil {
return err
} else if sr.Root != req.stateRoot {
return fmt.Errorf("state root mismatch: %s != %s", sr.Root, req.stateRoot)
}
}
// Save lastProposal for getVerified(). // Save lastProposal for getVerified().
s.lastProposal = req.transactionHashes s.lastProposal = req.transactionHashes
@ -584,6 +613,14 @@ func (s *service) newBlockFromContext(ctx *dbft.Context) block.Block {
block.Block.Network = s.network block.Block.Network = s.network
block.Block.Timestamp = ctx.Timestamp / nsInMs block.Block.Timestamp = ctx.Timestamp / nsInMs
block.Block.Index = ctx.BlockIndex block.Block.Index = ctx.BlockIndex
if s.stateRootEnabled {
sr, err := s.Chain.GetStateRoot(ctx.BlockIndex - 1)
if err != nil {
return nil
}
block.StateRootEnabled = true
block.PrevStateRoot = sr.Root
}
var validators keys.PublicKeys var validators keys.PublicKeys
var err error var err error

View file

@ -285,6 +285,31 @@ func TestService_getTx(t *testing.T) {
srv.Chain.Close() srv.Chain.Close()
} }
func TestService_PrepareRequest(t *testing.T) {
srv := newTestServiceWithState(t, true)
srv.dbft.Start()
defer srv.dbft.Timer.Stop()
priv, _ := getTestValidator(1)
p := new(Payload)
p.message = &message{}
p.SetValidatorIndex(1)
p.SetPayload(&prepareRequest{})
require.NoError(t, p.Sign(priv))
require.Error(t, srv.verifyRequest(p), "invalid stateroot setting")
p.SetPayload(&prepareRequest{stateRootEnabled: true})
require.NoError(t, p.Sign(priv))
require.Error(t, srv.verifyRequest(p), "invalid state root")
sr, err := srv.Chain.GetStateRoot(srv.dbft.BlockIndex - 1)
require.NoError(t, err)
p.SetPayload(&prepareRequest{stateRootEnabled: true, stateRoot: sr.Root})
require.NoError(t, p.Sign(priv))
require.NoError(t, srv.verifyRequest(p))
}
func TestService_OnPayload(t *testing.T) { func TestService_OnPayload(t *testing.T) {
srv := newTestService(t) srv := newTestService(t)
// This test directly reads things from srv.messages that normally // This test directly reads things from srv.messages that normally
@ -407,8 +432,12 @@ func shouldNotReceive(t *testing.T, ch chan Payload) {
} }
} }
func newTestServiceWithState(t *testing.T, stateRootInHeader bool) *service {
return newTestServiceWithChain(t, newTestChain(t, stateRootInHeader))
}
func newTestService(t *testing.T) *service { func newTestService(t *testing.T) *service {
return newTestServiceWithChain(t, newTestChain(t)) return newTestServiceWithState(t, false)
} }
func newTestServiceWithChain(t *testing.T, bc *core.Blockchain) *service { func newTestServiceWithChain(t *testing.T, bc *core.Blockchain) *service {
@ -445,9 +474,10 @@ func newSingleTestChain(t *testing.T) *core.Blockchain {
return chain return chain
} }
func newTestChain(t *testing.T) *core.Blockchain { func newTestChain(t *testing.T, stateRootInHeader bool) *core.Blockchain {
unitTestNetCfg, err := config.Load("../../config", netmode.UnitTestNet) unitTestNetCfg, err := config.Load("../../config", netmode.UnitTestNet)
require.NoError(t, err) require.NoError(t, err)
unitTestNetCfg.ProtocolConfiguration.StateRootInHeader = stateRootInHeader
chain, err := core.NewBlockchain(storage.NewMemoryStore(), unitTestNetCfg.ProtocolConfiguration, zaptest.NewLogger(t)) chain, err := core.NewBlockchain(storage.NewMemoryStore(), unitTestNetCfg.ProtocolConfiguration, zaptest.NewLogger(t))
require.NoError(t, err) require.NoError(t, err)

View file

@ -21,6 +21,8 @@ type (
ViewNumber byte ViewNumber byte
payload io.Serializable payload io.Serializable
// stateRootEnabled specifies if state root is exchanged during consensus.
stateRootEnabled bool
} }
// Payload is a type for consensus-related messages. // Payload is a type for consensus-related messages.
@ -302,7 +304,11 @@ func (m *message) DecodeBinary(r *io.BinReader) {
cv.newViewNumber = m.ViewNumber + 1 cv.newViewNumber = m.ViewNumber + 1
m.payload = cv m.payload = cv
case prepareRequestType: case prepareRequestType:
m.payload = new(prepareRequest) r := new(prepareRequest)
if m.stateRootEnabled {
r.stateRootEnabled = true
}
m.payload = r
case prepareResponseType: case prepareResponseType:
m.payload = new(prepareResponse) m.payload = new(prepareResponse)
case commitType: case commitType:
@ -310,7 +316,11 @@ func (m *message) DecodeBinary(r *io.BinReader) {
case recoveryRequestType: case recoveryRequestType:
m.payload = new(recoveryRequest) m.payload = new(recoveryRequest)
case recoveryMessageType: case recoveryMessageType:
m.payload = new(recoveryMessage) r := new(recoveryMessage)
if m.stateRootEnabled {
r.stateRootEnabled = true
}
m.payload = r
default: default:
r.Err = fmt.Errorf("invalid type: 0x%02x", byte(m.Type)) r.Err = fmt.Errorf("invalid type: 0x%02x", byte(m.Type))
return return

View file

@ -104,12 +104,13 @@ func TestConsensusPayload_Serializable(t *testing.T) {
require.Nil(t, actual.message) require.Nil(t, actual.message)
actual.message = new(message) actual.message = new(message)
// message should now be decoded from actual.data byte array // message should now be decoded from actual.data byte array
actual.message = new(message)
assert.NoError(t, actual.decodeData()) assert.NoError(t, actual.decodeData())
assert.NotNil(t, actual.MarshalUnsigned()) assert.NotNil(t, actual.MarshalUnsigned())
require.Equal(t, p, actual) require.Equal(t, p, actual)
data = p.MarshalUnsigned() data = p.MarshalUnsigned()
pu := NewPayload(netmode.Magic(rand.Uint32())) pu := NewPayload(netmode.Magic(rand.Uint32()), false)
require.NoError(t, pu.UnmarshalUnsigned(data)) require.NoError(t, pu.UnmarshalUnsigned(data))
assert.NoError(t, pu.decodeData()) assert.NoError(t, pu.decodeData())
_ = pu.MarshalUnsigned() _ = pu.MarshalUnsigned()
@ -316,7 +317,7 @@ func TestPayload_Sign(t *testing.T) {
p := randomPayload(t, prepareRequestType) p := randomPayload(t, prepareRequestType)
h := priv.PublicKey().GetScriptHash() h := priv.PublicKey().GetScriptHash()
bc := newTestChain(t) bc := newTestChain(t, false)
defer bc.Close() defer bc.Close()
require.Error(t, bc.VerifyWitness(h, p, &p.Witness, payloadGasLimit)) require.Error(t, bc.VerifyWitness(h, p, &p.Witness, payloadGasLimit))
require.NoError(t, p.Sign(priv)) require.NoError(t, p.Sign(priv))

View file

@ -12,6 +12,8 @@ type prepareRequest struct {
timestamp uint64 timestamp uint64
nonce uint64 nonce uint64
transactionHashes []util.Uint256 transactionHashes []util.Uint256
stateRootEnabled bool
stateRoot util.Uint256
} }
var _ payload.PrepareRequest = (*prepareRequest)(nil) var _ payload.PrepareRequest = (*prepareRequest)(nil)
@ -21,6 +23,9 @@ func (p *prepareRequest) EncodeBinary(w *io.BinWriter) {
w.WriteU64LE(p.timestamp) w.WriteU64LE(p.timestamp)
w.WriteU64LE(p.nonce) w.WriteU64LE(p.nonce)
w.WriteArray(p.transactionHashes) w.WriteArray(p.transactionHashes)
if p.stateRootEnabled {
w.WriteBytes(p.stateRoot[:])
}
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
@ -28,6 +33,9 @@ func (p *prepareRequest) DecodeBinary(r *io.BinReader) {
p.timestamp = r.ReadU64LE() p.timestamp = r.ReadU64LE()
p.nonce = r.ReadU64LE() p.nonce = r.ReadU64LE()
r.ReadArray(&p.transactionHashes, block.MaxTransactionsPerBlock) r.ReadArray(&p.transactionHashes, block.MaxTransactionsPerBlock)
if p.stateRootEnabled {
r.ReadBytes(p.stateRoot[:])
}
} }
// Timestamp implements payload.PrepareRequest interface. // Timestamp implements payload.PrepareRequest interface.

View file

@ -16,6 +16,7 @@ type (
preparationPayloads []*preparationCompact preparationPayloads []*preparationCompact
commitPayloads []*commitCompact commitPayloads []*commitCompact
changeViewPayloads []*changeViewCompact changeViewPayloads []*changeViewCompact
stateRootEnabled bool
prepareRequest *message prepareRequest *message
} }
@ -47,7 +48,7 @@ func (m *recoveryMessage) DecodeBinary(r *io.BinReader) {
var hasReq = r.ReadBool() var hasReq = r.ReadBool()
if hasReq { if hasReq {
m.prepareRequest = new(message) m.prepareRequest = &message{stateRootEnabled: m.stateRootEnabled}
m.prepareRequest.DecodeBinary(r) m.prepareRequest.DecodeBinary(r)
if r.Err == nil && m.prepareRequest.Type != prepareRequestType { if r.Err == nil && m.prepareRequest.Type != prepareRequestType {
r.Err = errors.New("recovery message PrepareRequest has wrong type") r.Err = errors.New("recovery message PrepareRequest has wrong type")
@ -143,9 +144,10 @@ func (m *recoveryMessage) AddPayload(p payload.ConsensusPayload) {
switch p.Type() { switch p.Type() {
case payload.PrepareRequestType: case payload.PrepareRequestType:
m.prepareRequest = &message{ m.prepareRequest = &message{
Type: prepareRequestType, Type: prepareRequestType,
ViewNumber: p.ViewNumber(), ViewNumber: p.ViewNumber(),
payload: p.GetPrepareRequest().(*prepareRequest), payload: p.GetPrepareRequest().(*prepareRequest),
stateRootEnabled: m.stateRootEnabled,
} }
h := p.Hash() h := p.Hash()
m.preparationHash = &h m.preparationHash = &h
@ -291,9 +293,10 @@ func fromPayload(t messageType, recovery *Payload, p io.Serializable) *Payload {
return &Payload{ return &Payload{
network: recovery.network, network: recovery.network,
message: &message{ message: &message{
Type: t, Type: t,
ViewNumber: recovery.message.ViewNumber, ViewNumber: recovery.message.ViewNumber,
payload: p, payload: p,
stateRootEnabled: recovery.stateRootEnabled,
}, },
version: recovery.Version(), version: recovery.Version(),
prevHash: recovery.PrevHash(), prevHash: recovery.PrevHash(),

View file

@ -12,8 +12,17 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestRecoveryMessage_Setters(t *testing.T) { func TestRecoveryMessageSetters(t *testing.T) {
srv := newTestService(t) t.Run("NoStateRoot", func(t *testing.T) {
testRecoveryMessageSetters(t, false)
})
t.Run("WithStateRoot", func(t *testing.T) {
testRecoveryMessageSetters(t, true)
})
}
func testRecoveryMessageSetters(t *testing.T, enableStateRoot bool) {
srv := newTestServiceWithState(t, enableStateRoot)
defer srv.Chain.Close() defer srv.Chain.Close()
privs := make([]*privateKey, testchain.Size()) privs := make([]*privateKey, testchain.Size())
pubs := make([]crypto.PublicKey, testchain.Size()) pubs := make([]crypto.PublicKey, testchain.Size())
@ -21,8 +30,8 @@ func TestRecoveryMessage_Setters(t *testing.T) {
privs[i], pubs[i] = getTestValidator(i) privs[i], pubs[i] = getTestValidator(i)
} }
r := &recoveryMessage{} r := &recoveryMessage{stateRootEnabled: enableStateRoot}
p := NewPayload(netmode.UnitTestNet) p := NewPayload(netmode.UnitTestNet, enableStateRoot)
p.SetType(payload.RecoveryMessageType) p.SetType(payload.RecoveryMessageType)
p.SetPayload(r) p.SetPayload(r)
// sign payload to have verification script // sign payload to have verification script
@ -32,15 +41,16 @@ func TestRecoveryMessage_Setters(t *testing.T) {
timestamp: 87, timestamp: 87,
nonce: 321, nonce: 321,
transactionHashes: []util.Uint256{{1}}, transactionHashes: []util.Uint256{{1}},
stateRootEnabled: enableStateRoot,
} }
p1 := NewPayload(netmode.UnitTestNet) p1 := NewPayload(netmode.UnitTestNet, enableStateRoot)
p1.SetType(payload.PrepareRequestType) p1.SetType(payload.PrepareRequestType)
p1.SetPayload(req) p1.SetPayload(req)
p1.SetValidatorIndex(0) p1.SetValidatorIndex(0)
require.NoError(t, p1.Sign(privs[0])) require.NoError(t, p1.Sign(privs[0]))
t.Run("prepare response is added", func(t *testing.T) { t.Run("prepare response is added", func(t *testing.T) {
p2 := NewPayload(netmode.UnitTestNet) p2 := NewPayload(netmode.UnitTestNet, enableStateRoot)
p2.SetType(payload.PrepareResponseType) p2.SetType(payload.PrepareResponseType)
p2.SetPayload(&prepareResponse{ p2.SetPayload(&prepareResponse{
preparationHash: p1.Hash(), preparationHash: p1.Hash(),
@ -76,7 +86,7 @@ func TestRecoveryMessage_Setters(t *testing.T) {
}) })
t.Run("change view is added", func(t *testing.T) { t.Run("change view is added", func(t *testing.T) {
p3 := NewPayload(netmode.UnitTestNet) p3 := NewPayload(netmode.UnitTestNet, enableStateRoot)
p3.SetType(payload.ChangeViewType) p3.SetType(payload.ChangeViewType)
p3.SetPayload(&changeView{ p3.SetPayload(&changeView{
newViewNumber: 1, newViewNumber: 1,
@ -98,7 +108,7 @@ func TestRecoveryMessage_Setters(t *testing.T) {
}) })
t.Run("commit is added", func(t *testing.T) { t.Run("commit is added", func(t *testing.T) {
p4 := NewPayload(netmode.UnitTestNet) p4 := NewPayload(netmode.UnitTestNet, enableStateRoot)
p4.SetType(payload.CommitType) p4.SetType(payload.CommitType)
p4.SetPayload(randomMessage(t, commitType)) p4.SetPayload(randomMessage(t, commitType))
p4.SetValidatorIndex(3) p4.SetValidatorIndex(3)

View file

@ -77,10 +77,11 @@ func (b *Block) RebuildMerkleRoot() {
// This is commonly used to create a block from stored data. // This is commonly used to create a block from stored data.
// Blocks created from trimmed data will have their Trimmed field // Blocks created from trimmed data will have their Trimmed field
// set to true. // set to true.
func NewBlockFromTrimmedBytes(network netmode.Magic, b []byte) (*Block, error) { func NewBlockFromTrimmedBytes(network netmode.Magic, stateRootEnabled bool, b []byte) (*Block, error) {
block := &Block{ block := &Block{
Base: Base{ Base: Base{
Network: network, Network: network,
StateRootEnabled: stateRootEnabled,
}, },
Trimmed: true, Trimmed: true,
} }
@ -113,10 +114,11 @@ func NewBlockFromTrimmedBytes(network netmode.Magic, b []byte) (*Block, error) {
} }
// New creates a new blank block tied to the specific network. // New creates a new blank block tied to the specific network.
func New(network netmode.Magic) *Block { func New(network netmode.Magic, stateRootEnabled bool) *Block {
return &Block{ return &Block{
Base: Base{ Base: Base{
Network: network, Network: network,
StateRootEnabled: stateRootEnabled,
}, },
} }
} }

View file

@ -43,6 +43,11 @@ type Base struct {
// necessary for correct signing/verification. // necessary for correct signing/verification.
Network netmode.Magic Network netmode.Magic
// StateRootEnabled specifies if header contains state root.
StateRootEnabled bool
// PrevStateRoot is state root of the previous block.
PrevStateRoot util.Uint256
// Hash of this block, created when binary encoded (double SHA256). // Hash of this block, created when binary encoded (double SHA256).
hash util.Uint256 hash util.Uint256
@ -61,6 +66,7 @@ type baseAux struct {
Timestamp uint64 `json:"time"` Timestamp uint64 `json:"time"`
Index uint32 `json:"index"` Index uint32 `json:"index"`
NextConsensus string `json:"nextconsensus"` NextConsensus string `json:"nextconsensus"`
PrevStateRoot *util.Uint256 `json:"previousstateroot,omitempty"`
Witnesses []transaction.Witness `json:"witnesses"` Witnesses []transaction.Witness `json:"witnesses"`
} }
@ -130,6 +136,9 @@ func (b *Base) encodeHashableFields(bw *io.BinWriter) {
bw.WriteU64LE(b.Timestamp) bw.WriteU64LE(b.Timestamp)
bw.WriteU32LE(b.Index) bw.WriteU32LE(b.Index)
bw.WriteBytes(b.NextConsensus[:]) bw.WriteBytes(b.NextConsensus[:])
if b.StateRootEnabled {
bw.WriteBytes(b.PrevStateRoot[:])
}
} }
// decodeHashableFields decodes the fields used for hashing. // decodeHashableFields decodes the fields used for hashing.
@ -141,6 +150,9 @@ func (b *Base) decodeHashableFields(br *io.BinReader) {
b.Timestamp = br.ReadU64LE() b.Timestamp = br.ReadU64LE()
b.Index = br.ReadU32LE() b.Index = br.ReadU32LE()
br.ReadBytes(b.NextConsensus[:]) br.ReadBytes(b.NextConsensus[:])
if b.StateRootEnabled {
br.ReadBytes(b.PrevStateRoot[:])
}
// Make the hash of the block here so we dont need to do this // Make the hash of the block here so we dont need to do this
// again. // again.
@ -161,6 +173,9 @@ func (b Base) MarshalJSON() ([]byte, error) {
NextConsensus: address.Uint160ToString(b.NextConsensus), NextConsensus: address.Uint160ToString(b.NextConsensus),
Witnesses: []transaction.Witness{b.Script}, Witnesses: []transaction.Witness{b.Script},
} }
if b.StateRootEnabled {
aux.PrevStateRoot = &b.PrevStateRoot
}
return json.Marshal(aux) return json.Marshal(aux)
} }
@ -188,6 +203,12 @@ func (b *Base) UnmarshalJSON(data []byte) error {
b.Index = aux.Index b.Index = aux.Index
b.NextConsensus = nextC b.NextConsensus = nextC
b.Script = aux.Witnesses[0] b.Script = aux.Witnesses[0]
if b.StateRootEnabled {
if aux.PrevStateRoot == nil {
return errors.New("'previousstateroot' is empty")
}
b.PrevStateRoot = *aux.PrevStateRoot
}
if !aux.Hash.Equals(b.Hash()) { if !aux.Hash.Equals(b.Hash()) {
return errors.New("json 'hash' doesn't match block hash") return errors.New("json 'hash' doesn't match block hash")
} }

View file

@ -31,7 +31,7 @@ func TestDecodeBlock1(t *testing.T) {
b, err := hex.DecodeString(data["raw"].(string)) b, err := hex.DecodeString(data["raw"].(string))
require.NoError(t, err) require.NoError(t, err)
block := New(netmode.TestNet) block := New(netmode.TestNet, false)
assert.NoError(t, testserdes.DecodeBinary(b, block)) assert.NoError(t, testserdes.DecodeBinary(b, block))
assert.Equal(t, uint32(data["index"].(float64)), block.Index) assert.Equal(t, uint32(data["index"].(float64)), block.Index)
@ -58,7 +58,7 @@ func TestTrimmedBlock(t *testing.T) {
b, err := block.Trim() b, err := block.Trim()
require.NoError(t, err) require.NoError(t, err)
trimmedBlock, err := NewBlockFromTrimmedBytes(netmode.TestNet, b) trimmedBlock, err := NewBlockFromTrimmedBytes(netmode.TestNet, false, b)
require.NoError(t, err) require.NoError(t, err)
assert.True(t, trimmedBlock.Trimmed) assert.True(t, trimmedBlock.Trimmed)
@ -114,7 +114,7 @@ func TestBinBlockDecodeEncode(t *testing.T) {
rawtx := "0000000005440c786a66aaebf472aacb1d1db19d5b494c6a9226ea91bf5cf0e63a6605138cde5064efb81bc6539620b9e6d6d7c74f97d415b922c4fb4bb1833ce6a97a9d61f962fb7301000065f000005d12ac6c589d59f92e82d8bf60659cb716ffc1f101fd4a010c4011ff5d2138cf546d112ef712ee8a15277f7b6f1d5d2564b97497ac155782e6089cd3005dc9de81a8b22bb2f1c3a2edbac55e01581cb27980fdedf3a8bc57fa470c40657253c374a48da773fc653591f282a63a60695f29ab6c86300020ed505a019e5563e1be493efa71bdde37b16b4ec3f5f6dc2d2a2550151b020176b4dbe7afe40c403efdc559cb6bff135fd79138267db897c6fded01e3a0f15c0fb1c337359935d65e7ac49239f020951a74a96e11e73d225c9789953ffec40d5f7c9a84707b1d9a0c402804f24ab8034fa41223977ba48883eb94951184e31e5739872daf4f65461de3196ebf333f6d7dc4aff0b7b2143793179415f50a715484aba4e33b97dc636e150c40ed6b2ffeaef97eef746815ad16f5b8aed743892e93f7216bb744eb5c2f4cad91ae291919b61cd9a8d50fe85630d5e010c49a01ed687727c3ae5a7e17d4da213afdfd00150c2103009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a20c21030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba0c210214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff010c2103408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a2594778060c2102a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b0c2102ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd0c2102f889ecd43c5126ff1932d75fa87dea34fc95325fb724db93c8f79fe32cc3f180170b41138defaf0202c1353ed4e94d0cbc00be80024f7673890000000000261c130000000000e404210001f813c2cc8e18bbe4b3b87f8ef9105b50bb93918e01005d0300743ba40b0000000c14aa07cc3f2193a973904a09a6e60b87f1f96273970c14f813c2cc8e18bbe4b3b87f8ef9105b50bb93918e13c00c087472616e736665720c14bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e6641627d5b523801420c402360bbf64b9644c25f066dbd406454b07ab9f56e8e25d92d90c96c598f6c29d97eabdcf226f3575481662cfcdd064ee410978e5fae3f09a2f83129ba9cd82641290c2103caf763f91d3691cba5b5df3eb13e668fdace0295b37e2e259fd0fb152d354f900b4195440d78" rawtx := "0000000005440c786a66aaebf472aacb1d1db19d5b494c6a9226ea91bf5cf0e63a6605138cde5064efb81bc6539620b9e6d6d7c74f97d415b922c4fb4bb1833ce6a97a9d61f962fb7301000065f000005d12ac6c589d59f92e82d8bf60659cb716ffc1f101fd4a010c4011ff5d2138cf546d112ef712ee8a15277f7b6f1d5d2564b97497ac155782e6089cd3005dc9de81a8b22bb2f1c3a2edbac55e01581cb27980fdedf3a8bc57fa470c40657253c374a48da773fc653591f282a63a60695f29ab6c86300020ed505a019e5563e1be493efa71bdde37b16b4ec3f5f6dc2d2a2550151b020176b4dbe7afe40c403efdc559cb6bff135fd79138267db897c6fded01e3a0f15c0fb1c337359935d65e7ac49239f020951a74a96e11e73d225c9789953ffec40d5f7c9a84707b1d9a0c402804f24ab8034fa41223977ba48883eb94951184e31e5739872daf4f65461de3196ebf333f6d7dc4aff0b7b2143793179415f50a715484aba4e33b97dc636e150c40ed6b2ffeaef97eef746815ad16f5b8aed743892e93f7216bb744eb5c2f4cad91ae291919b61cd9a8d50fe85630d5e010c49a01ed687727c3ae5a7e17d4da213afdfd00150c2103009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a20c21030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba0c210214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff010c2103408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a2594778060c2102a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b0c2102ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd0c2102f889ecd43c5126ff1932d75fa87dea34fc95325fb724db93c8f79fe32cc3f180170b41138defaf0202c1353ed4e94d0cbc00be80024f7673890000000000261c130000000000e404210001f813c2cc8e18bbe4b3b87f8ef9105b50bb93918e01005d0300743ba40b0000000c14aa07cc3f2193a973904a09a6e60b87f1f96273970c14f813c2cc8e18bbe4b3b87f8ef9105b50bb93918e13c00c087472616e736665720c14bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e6641627d5b523801420c402360bbf64b9644c25f066dbd406454b07ab9f56e8e25d92d90c96c598f6c29d97eabdcf226f3575481662cfcdd064ee410978e5fae3f09a2f83129ba9cd82641290c2103caf763f91d3691cba5b5df3eb13e668fdace0295b37e2e259fd0fb152d354f900b4195440d78"
rawtxBytes, _ := hex.DecodeString(rawtx) rawtxBytes, _ := hex.DecodeString(rawtx)
b := New(netmode.TestNet) b := New(netmode.TestNet, false)
assert.NoError(t, testserdes.DecodeBinary(rawtxBytes, b)) assert.NoError(t, testserdes.DecodeBinary(rawtxBytes, b))
expected := map[string]bool{ // 1 trans expected := map[string]bool{ // 1 trans
@ -150,7 +150,7 @@ func TestBinBlockDecodeEncode(t *testing.T) {
// update hidden hash value. // update hidden hash value.
_ = b.ConsensusData.Hash() _ = b.ConsensusData.Hash()
testserdes.MarshalUnmarshalJSON(t, b, New(netmode.TestNet)) testserdes.MarshalUnmarshalJSON(t, b, New(netmode.TestNet, false))
} }
func TestBlockSizeCalculation(t *testing.T) { func TestBlockSizeCalculation(t *testing.T) {
@ -163,7 +163,7 @@ func TestBlockSizeCalculation(t *testing.T) {
rawBlock := "0000000005440c786a66aaebf472aacb1d1db19d5b494c6a9226ea91bf5cf0e63a6605138cde5064efb81bc6539620b9e6d6d7c74f97d415b922c4fb4bb1833ce6a97a9d61f962fb7301000065f000005d12ac6c589d59f92e82d8bf60659cb716ffc1f101fd4a010c4011ff5d2138cf546d112ef712ee8a15277f7b6f1d5d2564b97497ac155782e6089cd3005dc9de81a8b22bb2f1c3a2edbac55e01581cb27980fdedf3a8bc57fa470c40657253c374a48da773fc653591f282a63a60695f29ab6c86300020ed505a019e5563e1be493efa71bdde37b16b4ec3f5f6dc2d2a2550151b020176b4dbe7afe40c403efdc559cb6bff135fd79138267db897c6fded01e3a0f15c0fb1c337359935d65e7ac49239f020951a74a96e11e73d225c9789953ffec40d5f7c9a84707b1d9a0c402804f24ab8034fa41223977ba48883eb94951184e31e5739872daf4f65461de3196ebf333f6d7dc4aff0b7b2143793179415f50a715484aba4e33b97dc636e150c40ed6b2ffeaef97eef746815ad16f5b8aed743892e93f7216bb744eb5c2f4cad91ae291919b61cd9a8d50fe85630d5e010c49a01ed687727c3ae5a7e17d4da213afdfd00150c2103009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a20c21030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba0c210214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff010c2103408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a2594778060c2102a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b0c2102ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd0c2102f889ecd43c5126ff1932d75fa87dea34fc95325fb724db93c8f79fe32cc3f180170b41138defaf0202c1353ed4e94d0cbc00be80024f7673890000000000261c130000000000e404210001f813c2cc8e18bbe4b3b87f8ef9105b50bb93918e01005d0300743ba40b0000000c14aa07cc3f2193a973904a09a6e60b87f1f96273970c14f813c2cc8e18bbe4b3b87f8ef9105b50bb93918e13c00c087472616e736665720c14bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e6641627d5b523801420c402360bbf64b9644c25f066dbd406454b07ab9f56e8e25d92d90c96c598f6c29d97eabdcf226f3575481662cfcdd064ee410978e5fae3f09a2f83129ba9cd82641290c2103caf763f91d3691cba5b5df3eb13e668fdace0295b37e2e259fd0fb152d354f900b4195440d78" rawBlock := "0000000005440c786a66aaebf472aacb1d1db19d5b494c6a9226ea91bf5cf0e63a6605138cde5064efb81bc6539620b9e6d6d7c74f97d415b922c4fb4bb1833ce6a97a9d61f962fb7301000065f000005d12ac6c589d59f92e82d8bf60659cb716ffc1f101fd4a010c4011ff5d2138cf546d112ef712ee8a15277f7b6f1d5d2564b97497ac155782e6089cd3005dc9de81a8b22bb2f1c3a2edbac55e01581cb27980fdedf3a8bc57fa470c40657253c374a48da773fc653591f282a63a60695f29ab6c86300020ed505a019e5563e1be493efa71bdde37b16b4ec3f5f6dc2d2a2550151b020176b4dbe7afe40c403efdc559cb6bff135fd79138267db897c6fded01e3a0f15c0fb1c337359935d65e7ac49239f020951a74a96e11e73d225c9789953ffec40d5f7c9a84707b1d9a0c402804f24ab8034fa41223977ba48883eb94951184e31e5739872daf4f65461de3196ebf333f6d7dc4aff0b7b2143793179415f50a715484aba4e33b97dc636e150c40ed6b2ffeaef97eef746815ad16f5b8aed743892e93f7216bb744eb5c2f4cad91ae291919b61cd9a8d50fe85630d5e010c49a01ed687727c3ae5a7e17d4da213afdfd00150c2103009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a20c21030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba0c210214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff010c2103408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a2594778060c2102a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b0c2102ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd0c2102f889ecd43c5126ff1932d75fa87dea34fc95325fb724db93c8f79fe32cc3f180170b41138defaf0202c1353ed4e94d0cbc00be80024f7673890000000000261c130000000000e404210001f813c2cc8e18bbe4b3b87f8ef9105b50bb93918e01005d0300743ba40b0000000c14aa07cc3f2193a973904a09a6e60b87f1f96273970c14f813c2cc8e18bbe4b3b87f8ef9105b50bb93918e13c00c087472616e736665720c14bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e6641627d5b523801420c402360bbf64b9644c25f066dbd406454b07ab9f56e8e25d92d90c96c598f6c29d97eabdcf226f3575481662cfcdd064ee410978e5fae3f09a2f83129ba9cd82641290c2103caf763f91d3691cba5b5df3eb13e668fdace0295b37e2e259fd0fb152d354f900b4195440d78"
rawBlockBytes, _ := hex.DecodeString(rawBlock) rawBlockBytes, _ := hex.DecodeString(rawBlock)
b := New(netmode.TestNet) b := New(netmode.TestNet, false)
assert.NoError(t, testserdes.DecodeBinary(rawBlockBytes, b)) assert.NoError(t, testserdes.DecodeBinary(rawBlockBytes, b))
expected := []struct { expected := []struct {

View file

@ -6,12 +6,13 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestHeaderEncodeDecode(t *testing.T) { func testHeaderEncodeDecode(t *testing.T, stateRootEnabled bool) {
header := Header{Base: Base{ header := Header{Base: Base{
Version: 0, Version: 0,
PrevHash: hash.Sha256([]byte("prevhash")), PrevHash: hash.Sha256([]byte("prevhash")),
@ -24,9 +25,13 @@ func TestHeaderEncodeDecode(t *testing.T) {
VerificationScript: []byte{0x11}, VerificationScript: []byte{0x11},
}, },
}} }}
if stateRootEnabled {
header.StateRootEnabled = stateRootEnabled
header.PrevStateRoot = random.Uint256()
}
_ = header.Hash() _ = header.Hash()
headerDecode := &Header{} headerDecode := &Header{Base: Base{StateRootEnabled: stateRootEnabled}}
testserdes.EncodeDecodeBinary(t, &header, headerDecode) testserdes.EncodeDecodeBinary(t, &header, headerDecode)
assert.Equal(t, header.Version, headerDecode.Version, "expected both versions to be equal") assert.Equal(t, header.Version, headerDecode.Version, "expected both versions to be equal")
@ -36,4 +41,14 @@ func TestHeaderEncodeDecode(t *testing.T) {
assert.Equal(t, header.NextConsensus, headerDecode.NextConsensus, "expected both next consensus fields to be equal") assert.Equal(t, header.NextConsensus, headerDecode.NextConsensus, "expected both next consensus fields to be equal")
assert.Equal(t, header.Script.InvocationScript, headerDecode.Script.InvocationScript, "expected equal invocation scripts") assert.Equal(t, header.Script.InvocationScript, headerDecode.Script.InvocationScript, "expected equal invocation scripts")
assert.Equal(t, header.Script.VerificationScript, headerDecode.Script.VerificationScript, "expected equal verification scripts") assert.Equal(t, header.Script.VerificationScript, headerDecode.Script.VerificationScript, "expected equal verification scripts")
assert.Equal(t, header.PrevStateRoot, headerDecode.PrevStateRoot, "expected equal state roots")
}
func TestHeaderEncodeDecode(t *testing.T) {
t.Run("NoStateRoot", func(t *testing.T) {
testHeaderEncodeDecode(t, false)
})
t.Run("WithStateRoot", func(t *testing.T) {
testHeaderEncodeDecode(t, true)
})
} }

View file

@ -19,7 +19,7 @@ func getDecodedBlock(t *testing.T, i int) *Block {
b, err := hex.DecodeString(data["raw"].(string)) b, err := hex.DecodeString(data["raw"].(string))
require.NoError(t, err) require.NoError(t, err)
block := New(netmode.TestNet) block := New(netmode.TestNet, false)
require.NoError(t, testserdes.DecodeBinary(b, block)) require.NoError(t, testserdes.DecodeBinary(b, block))
return block return block

View file

@ -160,7 +160,7 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
} }
bc := &Blockchain{ bc := &Blockchain{
config: cfg, config: cfg,
dao: dao.NewSimple(s, cfg.Magic), dao: dao.NewSimple(s, cfg.Magic, cfg.StateRootInHeader),
stopCh: make(chan struct{}), stopCh: make(chan struct{}),
runToExitCh: make(chan struct{}), runToExitCh: make(chan struct{}),
memPool: mempool.New(cfg.MemPoolSize), memPool: mempool.New(cfg.MemPoolSize),
@ -429,6 +429,16 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
if expectedHeight != block.Index { if expectedHeight != block.Index {
return fmt.Errorf("expected %d, got %d: %w", expectedHeight, block.Index, ErrInvalidBlockIndex) return fmt.Errorf("expected %d, got %d: %w", expectedHeight, block.Index, ErrInvalidBlockIndex)
} }
if bc.config.StateRootInHeader != block.StateRootEnabled {
return fmt.Errorf("%w: %v != %v",
ErrHdrStateRootSetting, bc.config.StateRootInHeader, block.StateRootEnabled)
}
if bc.config.StateRootInHeader {
if sr := bc.dao.MPT.StateRoot(); block.PrevStateRoot != sr {
return fmt.Errorf("%w: %s != %s",
ErrHdrInvalidStateRoot, block.PrevStateRoot.StringLE(), sr.StringLE())
}
}
if block.Index == bc.HeaderHeight()+1 { if block.Index == bc.HeaderHeight()+1 {
err := bc.addHeaders(bc.config.VerifyBlocks, block.Header()) err := bc.addHeaders(bc.config.VerifyBlocks, block.Header())
@ -1218,6 +1228,8 @@ var (
ErrHdrHashMismatch = errors.New("previous header hash doesn't match") ErrHdrHashMismatch = errors.New("previous header hash doesn't match")
ErrHdrIndexMismatch = errors.New("previous header index doesn't match") ErrHdrIndexMismatch = errors.New("previous header index doesn't match")
ErrHdrInvalidTimestamp = errors.New("block is not newer than the previous one") ErrHdrInvalidTimestamp = errors.New("block is not newer than the previous one")
ErrHdrStateRootSetting = errors.New("state root setting mismatch")
ErrHdrInvalidStateRoot = errors.New("state root for previous block is invalid")
) )
func (bc *Blockchain) verifyHeader(currHeader, prevHeader *block.Header) error { func (bc *Blockchain) verifyHeader(currHeader, prevHeader *block.Header) error {

View file

@ -122,6 +122,33 @@ func TestAddBlock(t *testing.T) {
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash()) assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
} }
func TestAddBlockStateRoot(t *testing.T) {
bc := newTestChainWithStateRoot(t, true)
defer bc.Close()
sr, err := bc.GetStateRoot(bc.BlockHeight())
require.NoError(t, err)
tx := newNEP5Transfer(bc.contracts.NEO.Hash, neoOwner, util.Uint160{}, 1)
tx.ValidUntilBlock = bc.BlockHeight() + 1
addSigners(tx)
require.NoError(t, signTx(bc, tx))
lastBlock := bc.topBlock.Load().(*block.Block)
b := newBlock(bc.config, lastBlock.Index+1, lastBlock.Hash(), tx)
err = bc.AddBlock(b)
require.True(t, errors.Is(err, ErrHdrStateRootSetting), "got: %v", err)
u := sr.Root
u[0] ^= 0xFF
b = newBlockWithState(bc.config, lastBlock.Index+1, lastBlock.Hash(), &u, tx)
err = bc.AddBlock(b)
require.True(t, errors.Is(err, ErrHdrInvalidStateRoot), "got: %v", err)
b = bc.newBlock(tx)
require.NoError(t, bc.AddBlock(b))
}
func TestAddBadBlock(t *testing.T) { func TestAddBadBlock(t *testing.T) {
bc := newTestChain(t) bc := newTestChain(t)
defer bc.Close() defer bc.Close()
@ -500,7 +527,7 @@ func TestVerifyTx(t *testing.T) {
InvocationScript: testchain.SignCommittee(txSetOracle.GetSignedPart()), InvocationScript: testchain.SignCommittee(txSetOracle.GetSignedPart()),
VerificationScript: testchain.CommitteeVerificationScript(), VerificationScript: testchain.CommitteeVerificationScript(),
}} }}
bl := block.New(netmode.UnitTestNet) bl := block.New(netmode.UnitTestNet, bc.config.StateRootInHeader)
bl.Index = bc.BlockHeight() + 1 bl.Index = bc.BlockHeight() + 1
ic := bc.newInteropContext(trigger.All, bc.dao, bl, txSetOracle) ic := bc.newInteropContext(trigger.All, bc.dao, bl, txSetOracle)
ic.SpawnVM() ic.SpawnVM()

View file

@ -15,7 +15,7 @@ import (
func TestCachedDaoContracts(t *testing.T) { func TestCachedDaoContracts(t *testing.T) {
store := storage.NewMemoryStore() store := storage.NewMemoryStore()
pdao := NewSimple(store, netmode.UnitTestNet) pdao := NewSimple(store, netmode.UnitTestNet, false)
dao := NewCached(pdao) dao := NewCached(pdao)
script := []byte{0xde, 0xad, 0xbe, 0xef} script := []byte{0xde, 0xad, 0xbe, 0xef}
@ -54,7 +54,7 @@ func TestCachedDaoContracts(t *testing.T) {
func TestCachedCachedDao(t *testing.T) { func TestCachedCachedDao(t *testing.T) {
store := storage.NewMemoryStore() store := storage.NewMemoryStore()
// Persistent DAO to check for backing storage. // Persistent DAO to check for backing storage.
pdao := NewSimple(store, netmode.UnitTestNet) pdao := NewSimple(store, netmode.UnitTestNet, false)
assert.NotEqual(t, store, pdao.Store) assert.NotEqual(t, store, pdao.Store)
// Cached DAO. // Cached DAO.
cdao := NewCached(pdao) cdao := NewCached(pdao)

View file

@ -76,12 +76,14 @@ type Simple struct {
MPT *mpt.Trie MPT *mpt.Trie
Store *storage.MemCachedStore Store *storage.MemCachedStore
network netmode.Magic network netmode.Magic
// stateRootInHeader specifies if block header contains state root.
stateRootInHeader bool
} }
// NewSimple creates new simple dao using provided backend store. // NewSimple creates new simple dao using provided backend store.
func NewSimple(backend storage.Store, network netmode.Magic) *Simple { func NewSimple(backend storage.Store, network netmode.Magic, stateRootInHeader bool) *Simple {
st := storage.NewMemCachedStore(backend) st := storage.NewMemCachedStore(backend)
return &Simple{Store: st, network: network} return &Simple{Store: st, network: network, stateRootInHeader: stateRootInHeader}
} }
// GetBatch returns currently accumulated DB changeset. // GetBatch returns currently accumulated DB changeset.
@ -92,7 +94,7 @@ func (dao *Simple) GetBatch() *storage.MemBatch {
// GetWrapped returns new DAO instance with another layer of wrapped // GetWrapped returns new DAO instance with another layer of wrapped
// MemCachedStore around the current DAO Store. // MemCachedStore around the current DAO Store.
func (dao *Simple) GetWrapped() DAO { func (dao *Simple) GetWrapped() DAO {
d := NewSimple(dao.Store, dao.network) d := NewSimple(dao.Store, dao.network, dao.stateRootInHeader)
d.MPT = dao.MPT d.MPT = dao.MPT
return d return d
} }
@ -514,7 +516,7 @@ func (dao *Simple) GetBlock(hash util.Uint256) (*block.Block, error) {
return nil, err return nil, err
} }
block, err := block.NewBlockFromTrimmedBytes(dao.network, b) block, err := block.NewBlockFromTrimmedBytes(dao.network, dao.stateRootInHeader, b)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -18,7 +18,7 @@ import (
) )
func TestPutGetAndDecode(t *testing.T) { func TestPutGetAndDecode(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
serializable := &TestSerializable{field: random.String(4)} serializable := &TestSerializable{field: random.String(4)}
hash := []byte{1} hash := []byte{1}
err := dao.Put(serializable, hash) err := dao.Put(serializable, hash)
@ -43,7 +43,7 @@ func (t *TestSerializable) DecodeBinary(reader *io.BinReader) {
} }
func TestPutAndGetContractState(t *testing.T) { func TestPutAndGetContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
contractState := &state.Contract{Script: []byte{}} contractState := &state.Contract{Script: []byte{}}
hash := contractState.ScriptHash() hash := contractState.ScriptHash()
err := dao.PutContractState(contractState) err := dao.PutContractState(contractState)
@ -54,7 +54,7 @@ func TestPutAndGetContractState(t *testing.T) {
} }
func TestDeleteContractState(t *testing.T) { func TestDeleteContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
contractState := &state.Contract{Script: []byte{}} contractState := &state.Contract{Script: []byte{}}
hash := contractState.ScriptHash() hash := contractState.ScriptHash()
err := dao.PutContractState(contractState) err := dao.PutContractState(contractState)
@ -67,7 +67,7 @@ func TestDeleteContractState(t *testing.T) {
} }
func TestSimple_GetAndUpdateNextContractID(t *testing.T) { func TestSimple_GetAndUpdateNextContractID(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
id, err := dao.GetAndUpdateNextContractID() id, err := dao.GetAndUpdateNextContractID()
require.NoError(t, err) require.NoError(t, err)
require.EqualValues(t, 0, id) require.EqualValues(t, 0, id)
@ -80,7 +80,7 @@ func TestSimple_GetAndUpdateNextContractID(t *testing.T) {
} }
func TestPutGetAppExecResult(t *testing.T) { func TestPutGetAppExecResult(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
hash := random.Uint256() hash := random.Uint256()
appExecResult := &state.AppExecResult{ appExecResult := &state.AppExecResult{
Container: hash, Container: hash,
@ -98,7 +98,7 @@ func TestPutGetAppExecResult(t *testing.T) {
} }
func TestPutGetStorageItem(t *testing.T) { func TestPutGetStorageItem(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
id := int32(random.Int(0, 1024)) id := int32(random.Int(0, 1024))
key := []byte{0} key := []byte{0}
storageItem := &state.StorageItem{Value: []uint8{}} storageItem := &state.StorageItem{Value: []uint8{}}
@ -109,7 +109,7 @@ func TestPutGetStorageItem(t *testing.T) {
} }
func TestDeleteStorageItem(t *testing.T) { func TestDeleteStorageItem(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
id := int32(random.Int(0, 1024)) id := int32(random.Int(0, 1024))
key := []byte{0} key := []byte{0}
storageItem := &state.StorageItem{Value: []uint8{}} storageItem := &state.StorageItem{Value: []uint8{}}
@ -122,7 +122,7 @@ func TestDeleteStorageItem(t *testing.T) {
} }
func TestGetBlock_NotExists(t *testing.T) { func TestGetBlock_NotExists(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
hash := random.Uint256() hash := random.Uint256()
block, err := dao.GetBlock(hash) block, err := dao.GetBlock(hash)
require.Error(t, err) require.Error(t, err)
@ -130,7 +130,7 @@ func TestGetBlock_NotExists(t *testing.T) {
} }
func TestPutGetBlock(t *testing.T) { func TestPutGetBlock(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
b := &block.Block{ b := &block.Block{
Base: block.Base{ Base: block.Base{
Script: transaction.Witness{ Script: transaction.Witness{
@ -148,14 +148,14 @@ func TestPutGetBlock(t *testing.T) {
} }
func TestGetVersion_NoVersion(t *testing.T) { func TestGetVersion_NoVersion(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
version, err := dao.GetVersion() version, err := dao.GetVersion()
require.Error(t, err) require.Error(t, err)
require.Equal(t, "", version) require.Equal(t, "", version)
} }
func TestGetVersion(t *testing.T) { func TestGetVersion(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
err := dao.PutVersion("testVersion") err := dao.PutVersion("testVersion")
require.NoError(t, err) require.NoError(t, err)
version, err := dao.GetVersion() version, err := dao.GetVersion()
@ -164,14 +164,14 @@ func TestGetVersion(t *testing.T) {
} }
func TestGetCurrentHeaderHeight_NoHeader(t *testing.T) { func TestGetCurrentHeaderHeight_NoHeader(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
height, err := dao.GetCurrentBlockHeight() height, err := dao.GetCurrentBlockHeight()
require.Error(t, err) require.Error(t, err)
require.Equal(t, uint32(0), height) require.Equal(t, uint32(0), height)
} }
func TestGetCurrentHeaderHeight_Store(t *testing.T) { func TestGetCurrentHeaderHeight_Store(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
b := &block.Block{ b := &block.Block{
Base: block.Base{ Base: block.Base{
Script: transaction.Witness{ Script: transaction.Witness{
@ -188,7 +188,7 @@ func TestGetCurrentHeaderHeight_Store(t *testing.T) {
} }
func TestStoreAsTransaction(t *testing.T) { func TestStoreAsTransaction(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 1) tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 1)
hash := tx.Hash() hash := tx.Hash()
err := dao.StoreAsTransaction(tx, 0, nil) err := dao.StoreAsTransaction(tx, 0, nil)

View file

@ -38,8 +38,13 @@ var neoOwner = testchain.MultisigScriptHash()
// newTestChain should be called before newBlock invocation to properly setup // newTestChain should be called before newBlock invocation to properly setup
// global state. // global state.
func newTestChain(t *testing.T) *Blockchain { func newTestChain(t *testing.T) *Blockchain {
return newTestChainWithStateRoot(t, false)
}
func newTestChainWithStateRoot(t *testing.T, stateRootInHeader bool) *Blockchain {
unitTestNetCfg, err := config.Load("../../config", testchain.Network()) unitTestNetCfg, err := config.Load("../../config", testchain.Network())
require.NoError(t, err) require.NoError(t, err)
unitTestNetCfg.ProtocolConfiguration.StateRootInHeader = stateRootInHeader
chain, err := NewBlockchain(storage.NewMemoryStore(), unitTestNetCfg.ProtocolConfiguration, zaptest.NewLogger(t)) chain, err := NewBlockchain(storage.NewMemoryStore(), unitTestNetCfg.ProtocolConfiguration, zaptest.NewLogger(t))
require.NoError(t, err) require.NoError(t, err)
go chain.Run() go chain.Run()
@ -48,10 +53,22 @@ func newTestChain(t *testing.T) *Blockchain {
func (bc *Blockchain) newBlock(txs ...*transaction.Transaction) *block.Block { func (bc *Blockchain) newBlock(txs ...*transaction.Transaction) *block.Block {
lastBlock := bc.topBlock.Load().(*block.Block) lastBlock := bc.topBlock.Load().(*block.Block)
if bc.config.StateRootInHeader {
sr, err := bc.GetStateRoot(bc.BlockHeight())
if err != nil {
panic(err)
}
return newBlockWithState(bc.config, lastBlock.Index+1, lastBlock.Hash(), &sr.Root, txs...)
}
return newBlock(bc.config, lastBlock.Index+1, lastBlock.Hash(), txs...) return newBlock(bc.config, lastBlock.Index+1, lastBlock.Hash(), txs...)
} }
func newBlock(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256, txs ...*transaction.Transaction) *block.Block { func newBlock(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256, txs ...*transaction.Transaction) *block.Block {
return newBlockWithState(cfg, index, prev, nil, txs...)
}
func newBlockWithState(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256,
prevState *util.Uint256, txs ...*transaction.Transaction) *block.Block {
validators, _ := validatorsFromConfig(cfg) validators, _ := validatorsFromConfig(cfg)
valScript, _ := smartcontract.CreateDefaultMultiSigRedeemScript(validators) valScript, _ := smartcontract.CreateDefaultMultiSigRedeemScript(validators)
witness := transaction.Witness{ witness := transaction.Witness{
@ -73,6 +90,10 @@ func newBlock(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256,
}, },
Transactions: txs, Transactions: txs,
} }
if prevState != nil {
b.StateRootEnabled = true
b.PrevStateRoot = *prevState
}
b.RebuildMerkleRoot() b.RebuildMerkleRoot()
b.Script.InvocationScript = testchain.Sign(b.GetSignedPart()) b.Script.InvocationScript = testchain.Sign(b.GetSignedPart())
return b return b
@ -99,7 +120,7 @@ func getDecodedBlock(t *testing.T, i int) *block.Block {
b, err := hex.DecodeString(data["raw"].(string)) b, err := hex.DecodeString(data["raw"].(string))
require.NoError(t, err) require.NoError(t, err)
block := block.New(testchain.Network()) block := block.New(testchain.Network(), false)
require.NoError(t, testserdes.DecodeBinary(b, block)) require.NoError(t, testserdes.DecodeBinary(b, block))
return block return block

View file

@ -144,7 +144,7 @@ func TestECDSAVerify(t *testing.T) {
chain := newTestChain(t) chain := newTestChain(t)
defer chain.Close() defer chain.Close()
ic := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) ic := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), nil, nil)
runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) { runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) {
ic.SpawnVM() ic.SpawnVM()
for i := range args { for i := range args {
@ -266,7 +266,7 @@ func TestRuntimeEncodeDecode(t *testing.T) {
func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) { func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) {
chain := newTestChain(t) chain := newTestChain(t)
context := chain.newInteropContext(trigger.Application, context := chain.newInteropContext(trigger.Application,
dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, chain.config.StateRootInHeader), nil, nil)
v := context.SpawnVM() v := context.SpawnVM()
return v, context, chain return v, context, chain
} }
@ -280,7 +280,8 @@ func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context,
func createVMAndBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) { func createVMAndBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
block := newDumbBlock() block := newDumbBlock()
chain := newTestChain(t) chain := newTestChain(t)
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), block, nil) d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, chain.GetConfig().StateRootInHeader)
context := chain.newInteropContext(trigger.Application, d, block, nil)
v := context.SpawnVM() v := context.SpawnVM()
return v, block, context, chain return v, block, context, chain
} }
@ -301,7 +302,8 @@ func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.C
} }
chain := newTestChain(t) chain := newTestChain(t)
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, chain.config.StateRootInHeader)
context := chain.newInteropContext(trigger.Application, d, nil, nil)
v := context.SpawnVM() v := context.SpawnVM()
return v, contractState, context, chain return v, contractState, context, chain
} }
@ -312,7 +314,8 @@ func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Con
tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3, 4}}} tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3, 4}}}
chain := newTestChain(t) chain := newTestChain(t)
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, tx) d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, chain.config.StateRootInHeader)
context := chain.newInteropContext(trigger.Application, d, nil, tx)
v := context.SpawnVM() v := context.SpawnVM()
return v, tx, context, chain return v, tx, context, chain
} }

View file

@ -19,7 +19,8 @@ func testNonInterop(t *testing.T, value interface{}, f func(*interop.Context) er
v.Estack().PushVal(value) v.Estack().PushVal(value)
chain := newTestChain(t) chain := newTestChain(t)
defer chain.Close() defer chain.Close()
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, chain.config.StateRootInHeader)
context := chain.newInteropContext(trigger.Application, d, nil, nil)
context.VM = v context.VM = v
require.Error(t, f(context)) require.Error(t, f(context))
} }

View file

@ -225,8 +225,8 @@ func TestNativeContract_InvokeInternal(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
ic := chain.newInteropContext(trigger.Application, d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, chain.config.StateRootInHeader)
dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil) ic := chain.newInteropContext(trigger.Application, d, nil, nil)
v := ic.SpawnVM() v := ic.SpawnVM()
t.Run("fail, bad current script hash", func(t *testing.T) { t.Run("fail, bad current script hash", func(t *testing.T) {

View file

@ -118,7 +118,7 @@ func TestDesignate_DesignateAsRole(t *testing.T) {
des := bc.contracts.Designate des := bc.contracts.Designate
tx := transaction.New(netmode.UnitTestNet, []byte{}, 0) tx := transaction.New(netmode.UnitTestNet, []byte{}, 0)
bl := block.New(netmode.UnitTestNet) bl := block.New(netmode.UnitTestNet, bc.config.StateRootInHeader)
bl.Index = bc.BlockHeight() + 1 bl.Index = bc.BlockHeight() + 1
ic := bc.newInteropContext(trigger.OnPersist, bc.dao, bl, tx) ic := bc.newInteropContext(trigger.OnPersist, bc.dao, bl, tx)
ic.SpawnVM() ic.SpawnVM()

View file

@ -142,7 +142,7 @@ func TestOracle_Request(t *testing.T) {
pub := priv.PublicKey() pub := priv.PublicKey()
tx := transaction.New(netmode.UnitTestNet, []byte{}, 0) tx := transaction.New(netmode.UnitTestNet, []byte{}, 0)
bl := block.New(netmode.UnitTestNet) bl := block.New(netmode.UnitTestNet, bc.config.StateRootInHeader)
bl.Index = bc.BlockHeight() + 1 bl.Index = bc.BlockHeight() + 1
setSigner(tx, testchain.CommitteeScriptHash()) setSigner(tx, testchain.CommitteeScriptHash())
ic := bc.newInteropContext(trigger.Application, bc.dao, bl, tx) ic := bc.newInteropContext(trigger.Application, bc.dao, bl, tx)

View file

@ -34,6 +34,9 @@ type Message struct {
// Network this message comes from, it has to be set upon Message // Network this message comes from, it has to be set upon Message
// creation for correct decoding. // creation for correct decoding.
Network netmode.Magic Network netmode.Magic
// StateRootInHeader specifies if state root is included in block header.
// This is needed for correct decoding.
StateRootInHeader bool
} }
// MessageFlag represents compression level of message payload // MessageFlag represents compression level of message payload
@ -106,7 +109,7 @@ func (m *Message) Decode(br *io.BinReader) error {
case CMDFilterClear, CMDGetAddr, CMDMempool, CMDVerack: case CMDFilterClear, CMDGetAddr, CMDMempool, CMDVerack:
m.Payload = payload.NewNullPayload() m.Payload = payload.NewNullPayload()
default: default:
return errors.New("unexpected empty payload") return fmt.Errorf("unexpected empty payload: %s", m.Command)
} }
return nil return nil
} }
@ -142,9 +145,9 @@ func (m *Message) decodePayload() error {
case CMDAddr: case CMDAddr:
p = &payload.AddressList{} p = &payload.AddressList{}
case CMDBlock: case CMDBlock:
p = block.New(m.Network) p = block.New(m.Network, m.StateRootInHeader)
case CMDConsensus: case CMDConsensus:
p = consensus.NewPayload(m.Network) p = consensus.NewPayload(m.Network, m.StateRootInHeader)
case CMDGetBlocks: case CMDGetBlocks:
p = &payload.GetBlocks{} p = &payload.GetBlocks{}
case CMDGetHeaders: case CMDGetHeaders:
@ -152,7 +155,7 @@ func (m *Message) decodePayload() error {
case CMDGetBlockByIndex: case CMDGetBlockByIndex:
p = &payload.GetBlockByIndex{} p = &payload.GetBlockByIndex{}
case CMDHeaders: case CMDHeaders:
p = &payload.Headers{Network: m.Network} p = &payload.Headers{Network: m.Network, StateRootInHeader: m.StateRootInHeader}
case CMDTX: case CMDTX:
p = &transaction.Transaction{Network: m.Network} p = &transaction.Transaction{Network: m.Network}
case CMDMerkleBlock: case CMDMerkleBlock:

View file

@ -12,6 +12,8 @@ import (
type Headers struct { type Headers struct {
Hdrs []*block.Header Hdrs []*block.Header
Network netmode.Magic Network netmode.Magic
// StateRootInHeader specifies whether header contains state root.
StateRootInHeader bool
} }
// Users can at most request 2k header. // Users can at most request 2k header.
@ -38,6 +40,7 @@ func (p *Headers) DecodeBinary(br *io.BinReader) {
for i := 0; i < int(lenHeaders); i++ { for i := 0; i < int(lenHeaders); i++ {
header := &block.Header{} header := &block.Header{}
header.Network = p.Network header.Network = p.Network
header.StateRootEnabled = p.StateRootInHeader
header.DecodeBinary(br) header.DecodeBinary(br)
p.Hdrs[i] = header p.Hdrs[i] = header
} }

View file

@ -55,6 +55,8 @@ type (
// Network's magic number for correct message decoding. // Network's magic number for correct message decoding.
network netmode.Magic network netmode.Magic
// stateRootInHeader specifies if block header contain state root.
stateRootInHeader bool
transport Transporter transport Transporter
discovery Discoverer discovery Discoverer
@ -95,17 +97,18 @@ func NewServer(config ServerConfig, chain blockchainer.Blockchainer, log *zap.Lo
} }
s := &Server{ s := &Server{
ServerConfig: config, ServerConfig: config,
chain: chain, chain: chain,
id: randomID(), id: randomID(),
network: chain.GetConfig().Magic, network: chain.GetConfig().Magic,
quit: make(chan struct{}), stateRootInHeader: chain.GetConfig().StateRootInHeader,
register: make(chan Peer), quit: make(chan struct{}),
unregister: make(chan peerDrop), register: make(chan Peer),
peers: make(map[Peer]bool), unregister: make(chan peerDrop),
consensusStarted: atomic.NewBool(false), peers: make(map[Peer]bool),
log: log, consensusStarted: atomic.NewBool(false),
transactions: make(chan *transaction.Transaction, 64), log: log,
transactions: make(chan *transaction.Transaction, 64),
} }
s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) { s.bQueue = newBlockQueue(maxBlockBatch, chain, log, func(b *block.Block) {
if !s.consensusStarted.Load() { if !s.consensusStarted.Load() {

View file

@ -150,7 +150,7 @@ func (p *TCPPeer) handleConn() {
if err == nil { if err == nil {
r := io.NewBinReaderFromIO(p.conn) r := io.NewBinReaderFromIO(p.conn)
for { for {
msg := &Message{Network: p.server.network} msg := &Message{Network: p.server.network, StateRootInHeader: p.server.stateRootInHeader}
err = msg.Decode(r) err = msg.Decode(r)
if err == payload.ErrTooManyHeaders { if err == payload.ErrTooManyHeaders {

View file

@ -28,14 +28,15 @@ const (
// Client represents the middleman for executing JSON RPC calls // Client represents the middleman for executing JSON RPC calls
// to remote NEO RPC nodes. // to remote NEO RPC nodes.
type Client struct { type Client struct {
cli *http.Client cli *http.Client
endpoint *url.URL endpoint *url.URL
network netmode.Magic network netmode.Magic
initDone bool stateRootInHeader bool
ctx context.Context initDone bool
opts Options ctx context.Context
requestF func(*request.Raw) (*response.Raw, error) opts Options
cache cache requestF func(*request.Raw) (*response.Raw, error)
cache cache
} }
// Options defines options for the RPC client. // Options defines options for the RPC client.
@ -115,6 +116,7 @@ func (c *Client) Init() error {
return fmt.Errorf("failed to get network magic: %w", err) return fmt.Errorf("failed to get network magic: %w", err)
} }
c.network = version.Magic c.network = version.Magic
c.stateRootInHeader = version.StateRootInHeader
neoContractHash, err := c.GetContractStateByAddressOrName("neo") neoContractHash, err := c.GetContractStateByAddressOrName("neo")
if err != nil { if err != nil {
return fmt.Errorf("failed to get NEO contract scripthash: %w", err) return fmt.Errorf("failed to get NEO contract scripthash: %w", err)

View file

@ -83,7 +83,7 @@ func (c *Client) getBlock(params request.RawParams) (*block.Block, error) {
return nil, err return nil, err
} }
r := io.NewBinReaderFromBuf(resp) r := io.NewBinReaderFromBuf(resp)
b = block.New(c.GetNetwork()) b = block.New(c.GetNetwork(), c.StateRootInHeader())
b.DecodeBinary(r) b.DecodeBinary(r)
if r.Err != nil { if r.Err != nil {
return nil, r.Err return nil, r.Err
@ -606,6 +606,11 @@ func (c *Client) GetNetwork() netmode.Magic {
return c.network return c.network
} }
// StateRootInHeader returns true if state root is contained in block header.
func (c *Client) StateRootInHeader() bool {
return c.stateRootInHeader
}
// GetNativeContractHash returns native contract hash by its name. It is not case-sensitive. // GetNativeContractHash returns native contract hash by its name. It is not case-sensitive.
func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) { func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) {
lowercasedName := strings.ToLower(name) lowercasedName := strings.ToLower(name)

View file

@ -61,7 +61,7 @@ func getResultBlock1() *result.Block {
if err != nil { if err != nil {
panic(err) panic(err)
} }
b := block.New(netmode.UnitTestNet) b := block.New(netmode.UnitTestNet, false)
err = testserdes.DecodeBinary(binB, b) err = testserdes.DecodeBinary(binB, b)
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -139,7 +139,7 @@ readloop:
var val interface{} var val interface{}
switch event { switch event {
case response.BlockEventID: case response.BlockEventID:
val = block.New(c.GetNetwork()) val = block.New(c.GetNetwork(), c.StateRootInHeader())
case response.TransactionEventID: case response.TransactionEventID:
val = &transaction.Transaction{Network: c.GetNetwork()} val = &transaction.Transaction{Network: c.GetNetwork()}
case response.NotificationEventID: case response.NotificationEventID:

View file

@ -11,5 +11,7 @@ type (
WSPort uint16 `json:"wsport,omitempty"` WSPort uint16 `json:"wsport,omitempty"`
Nonce uint32 `json:"nonce"` Nonce uint32 `json:"nonce"`
UserAgent string `json:"useragent"` UserAgent string `json:"useragent"`
// StateRootInHeader is true if state root is contained in block header.
StateRootInHeader bool `json:"staterootinheader,omitempty"`
} }
) )

View file

@ -39,13 +39,14 @@ type (
// Server represents the JSON-RPC 2.0 server. // Server represents the JSON-RPC 2.0 server.
Server struct { Server struct {
*http.Server *http.Server
chain blockchainer.Blockchainer chain blockchainer.Blockchainer
config rpc.Config config rpc.Config
network netmode.Magic network netmode.Magic
coreServer *network.Server stateRootEnabled bool
log *zap.Logger coreServer *network.Server
https *http.Server log *zap.Logger
shutdown chan struct{} https *http.Server
shutdown chan struct{}
subsLock sync.RWMutex subsLock sync.RWMutex
subscribers map[*subscriber]bool subscribers map[*subscriber]bool
@ -138,14 +139,15 @@ func New(chain blockchainer.Blockchainer, conf rpc.Config, coreServer *network.S
} }
return Server{ return Server{
Server: httpServer, Server: httpServer,
chain: chain, chain: chain,
config: conf, config: conf,
network: chain.GetConfig().Magic, network: chain.GetConfig().Magic,
coreServer: coreServer, stateRootEnabled: chain.GetConfig().StateRootInHeader,
log: log, coreServer: coreServer,
https: tlsServer, log: log,
shutdown: make(chan struct{}), https: tlsServer,
shutdown: make(chan struct{}),
subscribers: make(map[*subscriber]bool), subscribers: make(map[*subscriber]bool),
// These are NOT buffered to preserve original order of events. // These are NOT buffered to preserve original order of events.
@ -1039,7 +1041,7 @@ func (s *Server) submitBlock(reqParams request.Params) (interface{}, *response.E
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
b := block.New(s.network) b := block.New(s.network, s.stateRootEnabled)
r := io.NewBinReaderFromBuf(blockBytes) r := io.NewBinReaderFromBuf(blockBytes)
b.DecodeBinary(r) b.DecodeBinary(r)
if r.Err != nil { if r.Err != nil {

View file

@ -52,7 +52,7 @@ func getTestBlocks(t *testing.T) []*block.Block {
blocks := make([]*block.Block, 0, int(nBlocks)) blocks := make([]*block.Block, 0, int(nBlocks))
for i := 0; i < int(nBlocks); i++ { for i := 0; i < int(nBlocks); i++ {
_ = br.ReadU32LE() _ = br.ReadU32LE()
b := block.New(netmode.UnitTestNet) b := block.New(netmode.UnitTestNet, false)
b.DecodeBinary(br) b.DecodeBinary(br)
require.Nil(t, br.Err) require.Nil(t, br.Err)
blocks = append(blocks, b) blocks = append(blocks, b)