[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
|
||||
|
||||
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(¶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
|
||||
// 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
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