[chain] Refactor, add chaincfg and database initialisation (#243)

* [chain]

- Add basic chain cfg parameters

- Added logic to insert genesis block, if it is a fresh database

- changed SaveBlock to ProcessBlock
- changed SaveHeaders to ProcessHeaders

- Changed parameter from a wire message to the payload, for header and
block processing

- Added check in chain for when the block is in the future, i.e. not at
the tip of the chain

- Added custom error returns, to distinguish between a database error
and a validation error
This commit is contained in:
decentralisedkev 2019-03-28 20:23:50 +00:00 committed by GitHub
parent 8afec1ea45
commit 493d8f3d95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 149 additions and 34 deletions

View file

@ -1,9 +1,13 @@
package chain
import (
"errors"
"fmt"
"github.com/pkg/errors"
"github.com/CityOfZion/neo-go/pkg/chaincfg"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/CityOfZion/neo-go/pkg/database"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
@ -12,58 +16,89 @@ import (
var (
// ErrBlockAlreadyExists happens when you try to save the same block twice
ErrBlockAlreadyExists = errors.New("this block has already been saved in the database")
// ErrFutureBlock happens when you try to save a block that is not the next block sequentially
ErrFutureBlock = errors.New("this is not the next block sequentially, that should be added to the chain")
)
// Chain represents a blockchain instance
type Chain struct {
db *Chaindb
Db *Chaindb
}
//New returns a new chain instance
func New(db database.Database) *Chain {
return &Chain{
db: &Chaindb{db},
// New returns a new chain instance
func New(db database.Database, magic protocol.Magic) (*Chain, error) {
chain := &Chain{
Db: &Chaindb{db},
}
// Get last header saved to see if this is a fresh database
_, err := chain.Db.GetLastHeader()
if err == nil {
return chain, nil
}
if err != database.ErrNotFound {
return nil, err
}
// We have a database.ErrNotFound. Insert the genesisBlock
fmt.Printf("Starting a fresh database for %s\n", magic.String())
params, err := chaincfg.NetParams(magic)
if err != nil {
return nil, err
}
err = chain.Db.saveHeader(&params.GenesisBlock.BlockBase)
if err != nil {
return nil, err
}
err = chain.Db.saveBlock(params.GenesisBlock, true)
if err != nil {
return nil, err
}
return chain, nil
}
// SaveBlock verifies and saves the block in the database
// ProcessBlock verifies and saves the block in the database
// XXX: for now we will just save without verifying the block
// This function is called by the server and if an error is returned then
// the server informs the sync manager to redownload the block
// XXX:We should also check if the header is already saved in the database
// If not, then we need to validate the header with the rest of the chain
// For now we re-save the header
func (c *Chain) SaveBlock(msg payload.BlockMessage) error {
err := c.VerifyBlock(msg.Block)
if err != nil {
return err
}
//XXX(Issue): We can either check the hash here for genesisblock.
//We most likely will have it anyways after validation/ We can return it from VerifyBlock
// Or we can do it somewhere in startup, performance benefits
// won't be that big since it's just a bytes.Equal.
// so it's more about which is more readable and where it makes sense to put
return c.db.saveBlock(msg.Block, false)
}
func (c *Chain) ProcessBlock(block payload.Block) error {
// VerifyBlock verifies whether a block is valid according
// to the rules of consensus
func (c *Chain) VerifyBlock(block payload.Block) error {
// Check if we already have this block
// XXX: We can optimise by implementing a Has method
// Check if we already have this block saved
// XXX: We can optimise by implementing a Has() method
// caching the last block in memory
lastBlock, err := c.db.GetLastBlock()
lastBlock, err := c.Db.GetLastBlock()
if err != nil {
return err
}
// Check if we have already saved this block
// by looking if the latest block height is more than
// incoming block height
if lastBlock.Index > block.Index {
return ErrBlockAlreadyExists
}
if block.Index > lastBlock.Index+1 {
return ErrFutureBlock
}
err = c.verifyBlock(block)
if err != nil {
return ValidationError{err.Error()}
}
err = c.Db.saveBlock(block, false)
if err != nil {
return DatabaseError{err.Error()}
}
return nil
}
// VerifyBlock verifies whether a block is valid according
// to the rules of consensus
func (c *Chain) verifyBlock(block payload.Block) error {
return nil
}
@ -73,14 +108,18 @@ func (c *Chain) VerifyTx(tx transaction.Transactioner) error {
return nil
}
// SaveHeaders will save the set of headers without validating
func (c *Chain) SaveHeaders(msg payload.HeadersMessage) error {
// ProcessHeaders will save the set of headers without validating
func (c *Chain) ProcessHeaders(hdrs []*payload.BlockBase) error {
err := c.verifyHeaders(msg.Headers)
err := c.verifyHeaders(hdrs)
if err != nil {
return err
return ValidationError{err.Error()}
}
return c.db.saveHeaders(msg.Headers)
err = c.Db.saveHeaders(hdrs)
if err != nil {
return DatabaseError{err.Error()}
}
return nil
}
// verifyHeaders will be used to verify a batch of headers

19
pkg/chain/errors.go Normal file
View file

@ -0,0 +1,19 @@
package chain
// ValidationError occurs when verificatio of the object fails
type ValidationError struct {
msg string
}
func (v ValidationError) Error() string {
return v.msg
}
// DatabaseError occurs when the chain fails to save the object in the database
type DatabaseError struct {
msg string
}
func (d DatabaseError) Error() string {
return d.msg
}

44
pkg/chaincfg/chaincfg.go Normal file
View file

@ -0,0 +1,44 @@
package chaincfg
import (
"bytes"
"encoding/hex"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
)
// Params are the parameters needed to setup the network
type Params struct {
GenesisBlock payload.Block
}
//NetParams returns the parameters for the chosen network magic
func NetParams(magic protocol.Magic) (Params, error) {
switch magic {
case protocol.MainNet:
return mainnet()
default:
return mainnet()
}
}
//Mainnet returns the parameters needed for mainnet
func mainnet() (Params, error) {
rawHex := "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151"
rawBytes, err := hex.DecodeString(rawHex)
if err != nil {
return Params{}, err
}
reader := bytes.NewReader(rawBytes)
block := payload.Block{}
err = block.Decode(reader)
if err != nil {
return Params{}, err
}
return Params{
GenesisBlock: block,
}, nil
}

View file

@ -0,0 +1,13 @@
package chaincfg
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMainnet(t *testing.T) {
p, err := mainnet()
assert.Nil(t, err)
assert.Equal(t, p.GenesisBlock.Hash.ReverseString(), "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf")
}