forked from TrueCloudLab/neoneo-go
[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:
parent
8afec1ea45
commit
493d8f3d95
4 changed files with 149 additions and 34 deletions
|
@ -1,9 +1,13 @@
|
||||||
package chain
|
package chain
|
||||||
|
|
||||||
import (
|
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/payload/transaction"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/database"
|
"github.com/CityOfZion/neo-go/pkg/database"
|
||||||
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
"github.com/CityOfZion/neo-go/pkg/wire/payload"
|
||||||
|
@ -12,58 +16,89 @@ import (
|
||||||
var (
|
var (
|
||||||
// ErrBlockAlreadyExists happens when you try to save the same block twice
|
// ErrBlockAlreadyExists happens when you try to save the same block twice
|
||||||
ErrBlockAlreadyExists = errors.New("this block has already been saved in the database")
|
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
|
// Chain represents a blockchain instance
|
||||||
type Chain struct {
|
type Chain struct {
|
||||||
db *Chaindb
|
Db *Chaindb
|
||||||
}
|
}
|
||||||
|
|
||||||
//New returns a new chain instance
|
// New returns a new chain instance
|
||||||
func New(db database.Database) *Chain {
|
func New(db database.Database, magic protocol.Magic) (*Chain, error) {
|
||||||
return &Chain{
|
|
||||||
db: &Chaindb{db},
|
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(¶ms.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
|
// 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
|
// This function is called by the server and if an error is returned then
|
||||||
// the server informs the sync manager to redownload the block
|
// the server informs the sync manager to redownload the block
|
||||||
// XXX:We should also check if the header is already saved in the database
|
// 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
|
// If not, then we need to validate the header with the rest of the chain
|
||||||
// For now we re-save the header
|
// For now we re-save the header
|
||||||
func (c *Chain) SaveBlock(msg payload.BlockMessage) error {
|
func (c *Chain) ProcessBlock(block payload.Block) 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyBlock verifies whether a block is valid according
|
// Check if we already have this block saved
|
||||||
// to the rules of consensus
|
// XXX: We can optimise by implementing a Has() method
|
||||||
func (c *Chain) VerifyBlock(block payload.Block) error {
|
|
||||||
|
|
||||||
// Check if we already have this block
|
|
||||||
// XXX: We can optimise by implementing a Has method
|
|
||||||
// caching the last block in memory
|
// caching the last block in memory
|
||||||
lastBlock, err := c.db.GetLastBlock()
|
lastBlock, err := c.Db.GetLastBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if lastBlock.Index > block.Index {
|
||||||
return ErrBlockAlreadyExists
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,14 +108,18 @@ func (c *Chain) VerifyTx(tx transaction.Transactioner) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveHeaders will save the set of headers without validating
|
// ProcessHeaders will save the set of headers without validating
|
||||||
func (c *Chain) SaveHeaders(msg payload.HeadersMessage) error {
|
func (c *Chain) ProcessHeaders(hdrs []*payload.BlockBase) error {
|
||||||
|
|
||||||
err := c.verifyHeaders(msg.Headers)
|
err := c.verifyHeaders(hdrs)
|
||||||
if err != nil {
|
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
|
// verifyHeaders will be used to verify a batch of headers
|
||||||
|
|
19
pkg/chain/errors.go
Normal file
19
pkg/chain/errors.go
Normal 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
44
pkg/chaincfg/chaincfg.go
Normal 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
|
||||||
|
}
|
13
pkg/chaincfg/chaincfg_test.go
Normal file
13
pkg/chaincfg/chaincfg_test.go
Normal 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")
|
||||||
|
}
|
Loading…
Reference in a new issue