diff --git a/pkg/chain/chain.go b/pkg/chain/chain.go index c1492cb8f..c44764388 100644 --- a/pkg/chain/chain.go +++ b/pkg/chain/chain.go @@ -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 diff --git a/pkg/chain/errors.go b/pkg/chain/errors.go new file mode 100644 index 000000000..627a304ba --- /dev/null +++ b/pkg/chain/errors.go @@ -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 +} diff --git a/pkg/chaincfg/chaincfg.go b/pkg/chaincfg/chaincfg.go new file mode 100644 index 000000000..ff6024c6f --- /dev/null +++ b/pkg/chaincfg/chaincfg.go @@ -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 +} diff --git a/pkg/chaincfg/chaincfg_test.go b/pkg/chaincfg/chaincfg_test.go new file mode 100644 index 000000000..043350b81 --- /dev/null +++ b/pkg/chaincfg/chaincfg_test.go @@ -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") +}