diff --git a/account/state/state_cache.go b/account/state/state_cache.go index 75dd70b1cf33be132d2685990a2ade2d302fb466..869842431bc3213eda869d65103ddf384d13738f 100644 --- a/account/state/state_cache.go +++ b/account/state/state_cache.go @@ -193,7 +193,7 @@ func (cache *stateCache) Sync(state Writer) error { addresses = append(addresses, address) } - sort.Stable(addresses) + sort.Sort(addresses) for _, address := range addresses { accInfo := cache.accounts[address] accInfo.RLock() @@ -208,7 +208,7 @@ func (cache *stateCache) Sync(state Writer) error { keys = append(keys, key) } // First update keys - sort.Stable(keys) + sort.Sort(keys) for _, key := range keys { value := accInfo.storage[key] err := state.SetStorage(address, key, value) diff --git a/account/validator.go b/account/validator.go index 017b1eb2a180036f22665aaac2b8ff5fd6750ba6..121b76fd234a4b27303e47bbef4f1b1f83b80f13 100644 --- a/account/validator.go +++ b/account/validator.go @@ -10,9 +10,6 @@ type Validator interface { Addressable // The validator's voting power Power() uint64 - // Alter the validator's voting power by amount that can be negative or positive. - // A power of 0 effectively unbonds the validator - WithNewPower(uint64) Validator } // Neither abci_types or tm_types has quite the representation we want diff --git a/account/validator_test.go b/account/validator_test.go index ff660300316d650d7e3ab64ff89ae4b88effecd4..0bc76357b7691133223a361dd82e5f7dde55f285 100644 --- a/account/validator_test.go +++ b/account/validator_test.go @@ -1,14 +1 @@ package account - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAlterPower(t *testing.T) { - val := AsValidator(NewConcreteAccountFromSecret("seeeeecret").Account()) - valInc := val.WithNewPower(2442132) - assert.Equal(t, uint64(0), val.Power()) - assert.Equal(t, uint64(2442132), valInc.Power()) -} diff --git a/binary/integer.go b/binary/integer.go index cd57bf3a1b8ccf9536a31318b6c52d0f8a8cc624..5824a4a9a9efbdb0a596765dbbf004ede7ef02d4 100644 --- a/binary/integer.go +++ b/binary/integer.go @@ -81,8 +81,6 @@ func IsUint64SumOverflow(a, b uint64) bool { return math.MaxUint64-a < b } -// - // Converts a possibly negative big int x into a positive big int encoding a twos complement representation of x // truncated to 32 bytes func U256(x *big.Int) *big.Int { diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 74cbe338568a2bcf7ab16b3ece5065aa18f47129..daafb1ac10e88fb069502b29e574a06f4aad6bc0 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -18,81 +18,44 @@ import ( "bytes" "encoding/json" "fmt" - "sync" "time" + "sync" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/logging" dbm "github.com/tendermint/tmlibs/db" ) -var stateKey = []byte("BlockchainState") - -// Immutable Root of blockchain -type Root interface { - // GenesisHash precomputed from GenesisDoc - GenesisHash() []byte - GenesisDoc() genesis.GenesisDoc -} - -// Immutable pointer to the current tip of the blockchain -type Tip interface { - // ChainID precomputed from GenesisDoc - ChainID() string - // All Last* references are to the block last committed - LastBlockHeight() uint64 - LastBlockTime() time.Time - LastBlockHash() []byte - // Note this is the hash of the application state after the most recently committed block's transactions executed - // and so lastBlock.Header.AppHash will be one block older than our AppHashAfterLastBlock (i.e. Tendermint closes - // the AppHash we return from ABCI Commit into the _next_ block) - AppHashAfterLastBlock() []byte -} +// Blocks to average validator power over +const DefaultValidatorsWindowSize = 10 -// Burrow's portion of the Blockchain state -type Blockchain interface { - // Read locker - sync.Locker - Root - Tip - // Returns an immutable copy of the tip - Tip() Tip - // Returns a copy of the current validator set - Validators() []acm.Validator -} - -type MutableBlockchain interface { - Blockchain - CommitBlock(blockTime time.Time, blockHash, appHash []byte) error -} +var stateKey = []byte("BlockchainState") -type root struct { +type Root struct { genesisHash []byte genesisDoc genesis.GenesisDoc } -type tip struct { +type Tip struct { chainID string lastBlockHeight uint64 lastBlockTime time.Time lastBlockHash []byte appHashAfterLastBlock []byte + validators Validators + validatorsWindow ValidatorsWindow } -type blockchain struct { +type Blockchain struct { + Root + Tip sync.RWMutex db dbm.DB - *root - *tip - validators []acm.Validator } -var _ Root = &blockchain{} -var _ Tip = &blockchain{} -var _ Blockchain = &blockchain{} -var _ MutableBlockchain = &blockchain{} - type PersistedState struct { AppHashAfterLastBlock []byte LastBlockHeight uint64 @@ -100,23 +63,23 @@ type PersistedState struct { } func LoadOrNewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc, - logger *logging.Logger) (*blockchain, error) { + logger *logging.Logger) (*Blockchain, error) { logger = logger.WithScope("LoadOrNewBlockchain") logger.InfoMsg("Trying to load blockchain state from database", "database_key", stateKey) - blockchain, err := loadBlockchain(db) + bc, err := loadBlockchain(db) if err != nil { return nil, fmt.Errorf("error loading blockchain state from database: %v", err) } - if blockchain != nil { - dbHash := blockchain.genesisDoc.Hash() + if bc != nil { + dbHash := bc.genesisDoc.Hash() argHash := genesisDoc.Hash() if !bytes.Equal(dbHash, argHash) { return nil, fmt.Errorf("GenesisDoc passed to LoadOrNewBlockchain has hash: 0x%X, which does not "+ "match the one found in database: 0x%X", argHash, dbHash) } - return blockchain, nil + return bc, nil } logger.InfoMsg("No existing blockchain state found in database, making new blockchain") @@ -124,7 +87,7 @@ func LoadOrNewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc, } // Pointer to blockchain state initialised from genesis -func newBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc) *blockchain { +func newBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc) *Blockchain { var validators []acm.Validator for _, gv := range genesisDoc.Validators { validators = append(validators, acm.ConcreteValidator{ @@ -132,16 +95,15 @@ func newBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc) *blockchain { Power: uint64(gv.Amount), }.Validator()) } - root := NewRoot(genesisDoc) - return &blockchain{ - db: db, - root: root, - tip: NewTip(genesisDoc.ChainID(), root.genesisDoc.GenesisTime, root.genesisHash), - validators: validators, + rt := NewRoot(genesisDoc) + return &Blockchain{ + db: db, + Root: rt, + Tip: NewTip(genesisDoc.ChainID(), rt.genesisDoc.GenesisTime, rt.genesisHash), } } -func loadBlockchain(db dbm.DB) (*blockchain, error) { +func loadBlockchain(db dbm.DB) (*Blockchain, error) { buf := db.Get(stateKey) if len(buf) == 0 { return nil, nil @@ -150,29 +112,31 @@ func loadBlockchain(db dbm.DB) (*blockchain, error) { if err != nil { return nil, err } - blockchain := newBlockchain(db, &persistedState.GenesisDoc) - blockchain.lastBlockHeight = persistedState.LastBlockHeight - blockchain.appHashAfterLastBlock = persistedState.AppHashAfterLastBlock - return blockchain, nil + bc := newBlockchain(db, &persistedState.GenesisDoc) + bc.lastBlockHeight = persistedState.LastBlockHeight + bc.appHashAfterLastBlock = persistedState.AppHashAfterLastBlock + return bc, nil } -func NewRoot(genesisDoc *genesis.GenesisDoc) *root { - return &root{ +func NewRoot(genesisDoc *genesis.GenesisDoc) Root { + return Root{ genesisHash: genesisDoc.Hash(), genesisDoc: *genesisDoc, } } // Create genesis Tip -func NewTip(chainID string, genesisTime time.Time, genesisHash []byte) *tip { - return &tip{ +func NewTip(chainID string, genesisTime time.Time, genesisHash []byte) Tip { + return Tip{ chainID: chainID, lastBlockTime: genesisTime, appHashAfterLastBlock: genesisHash, + validators: NewValidators(), + validatorsWindow: NewValidatorsWindow(DefaultValidatorsWindowSize), } } -func (bc *blockchain) CommitBlock(blockTime time.Time, blockHash, appHash []byte) error { +func (bc *Blockchain) CommitBlock(blockTime time.Time, blockHash, appHash []byte) error { bc.Lock() defer bc.Unlock() bc.lastBlockHeight += 1 @@ -182,7 +146,7 @@ func (bc *blockchain) CommitBlock(blockTime time.Time, blockHash, appHash []byte return bc.save() } -func (bc *blockchain) save() error { +func (bc *Blockchain) save() error { if bc.db != nil { encodedState, err := bc.Encode() if err != nil { @@ -193,28 +157,7 @@ func (bc *blockchain) save() error { return nil } -func (bc *blockchain) Root() Root { - return bc.root -} - -func (bc *blockchain) Tip() Tip { - bc.RLock() - defer bc.RUnlock() - t := *bc.tip - return &t -} - -func (bc *blockchain) Validators() []acm.Validator { - bc.RLock() - defer bc.RUnlock() - vs := make([]acm.Validator, len(bc.validators)) - for i, v := range bc.validators { - vs[i] = v - } - return vs -} - -func (bc *blockchain) Encode() ([]byte, error) { +func (bc *Blockchain) Encode() ([]byte, error) { persistedState := &PersistedState{ GenesisDoc: bc.genesisDoc, AppHashAfterLastBlock: bc.appHashAfterLastBlock, @@ -236,30 +179,38 @@ func Decode(encodedState []byte) (*PersistedState, error) { return persistedState, nil } -func (r *root) GenesisHash() []byte { +func (r *Root) GenesisHash() []byte { return r.genesisHash } -func (r *root) GenesisDoc() genesis.GenesisDoc { +func (r *Root) GenesisDoc() genesis.GenesisDoc { return r.genesisDoc } -func (t *tip) ChainID() string { +func (t *Tip) ChainID() string { return t.chainID } -func (t *tip) LastBlockHeight() uint64 { +func (t *Tip) LastBlockHeight() uint64 { return t.lastBlockHeight } -func (t *tip) LastBlockTime() time.Time { +func (t *Tip) LastBlockTime() time.Time { return t.lastBlockTime } -func (t *tip) LastBlockHash() []byte { +func (t *Tip) LastBlockHash() []byte { return t.lastBlockHash } -func (t *tip) AppHashAfterLastBlock() []byte { +func (t *Tip) AppHashAfterLastBlock() []byte { return t.appHashAfterLastBlock } + +func (t *Tip) IterateValidators(iter func(publicKey crypto.PublicKey, power uint64) (stop bool)) (stopped bool) { + return t.validators.Iterate(iter) +} + +func (t *Tip) NumValidators() int { + return t.validators.Length() +} diff --git a/blockchain/validators.go b/blockchain/validators.go new file mode 100644 index 0000000000000000000000000000000000000000..0ba398af0cd0d2103664d84158223865134978bd --- /dev/null +++ b/blockchain/validators.go @@ -0,0 +1,122 @@ +package blockchain + +import ( + "fmt" + + "sort" + + "bytes" + "encoding/binary" + + burrowBinary "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/crypto" +) + +// A Validator multiset +type Validators struct { + power map[crypto.Address]uint64 + publicKey map[crypto.Address]crypto.PublicKey + totalPower uint64 +} + +func NewValidators() Validators { + return Validators{ + power: make(map[crypto.Address]uint64), + publicKey: make(map[crypto.Address]crypto.PublicKey), + } +} + +// Add the power of a validator +func (vs *Validators) AlterPower(publicKey crypto.PublicKey, power uint64) error { + address := publicKey.Address() + // Remove existing power (possibly 0) from total + vs.totalPower -= vs.power[address] + if burrowBinary.IsUint64SumOverflow(vs.totalPower, power) { + // Undo removing existing power + vs.totalPower += vs.power[address] + return fmt.Errorf("could not increase total validator power by %v from %v since that would overflow "+ + "uint64", power, vs.totalPower) + } + vs.publicKey[address] = publicKey + vs.power[address] = power + // Note we are adjusting by the difference in power (+/-) since we subtracted the previous amount above + vs.totalPower += power + return nil +} + +func (vs *Validators) AddPower(publicKey crypto.PublicKey, power uint64) error { + currentPower := vs.power[publicKey.Address()] + if burrowBinary.IsUint64SumOverflow(currentPower, power) { + return fmt.Errorf("could add power %v to validator %v with power %v because that would overflow uint64", + power, publicKey.Address(), currentPower) + } + return vs.AlterPower(publicKey, vs.power[publicKey.Address()]+power) +} + +func (vs *Validators) SubtractPower(publicKey crypto.PublicKey, power uint64) error { + currentPower := vs.power[publicKey.Address()] + if currentPower < power { + return fmt.Errorf("could subtract power %v from validator %v with power %v because that would "+ + "underflow uint64", power, publicKey.Address(), currentPower) + } + return vs.AlterPower(publicKey, vs.power[publicKey.Address()]-power) +} + +// Iterates over validators sorted by address +func (vs *Validators) Iterate(iter func(publicKey crypto.PublicKey, power uint64) (stop bool)) (stopped bool) { + addresses := make(crypto.Addresses, 0, len(vs.power)) + for address := range vs.power { + addresses = append(addresses, address) + } + sort.Sort(addresses) + for _, address := range addresses { + if iter(vs.publicKey[address], vs.power[address]) { + return true + } + } + return false +} + +func (vs *Validators) Length() int { + return len(vs.power) +} + +func (vs *Validators) TotalPower() uint64 { + return vs.totalPower +} + +// Uses the fixed width public key encoding to +func (vs *Validators) Encode() []byte { + buffer := new(bytes.Buffer) + // varint buffer + buf := make([]byte, 8) + vs.Iterate(func(publicKey crypto.PublicKey, power uint64) (stop bool) { + buffer.Write(publicKey.Encode()) + buffer.Write(buf[:binary.PutUvarint(buf, power)]) + return + }) + return buffer.Bytes() +} + +// Decodes validators encoded with Encode - expects the exact encoded size with no trailing bytes +func DecodeValidators(encoded []byte, validators *Validators) error { + publicKey := new(crypto.PublicKey) + i := 0 + for i < len(encoded) { + n, err := crypto.DecodePublicKeyFixedWidth(encoded[i:], publicKey) + if err != nil { + return err + } + i += n + power, n := binary.Uvarint(encoded[i:]) + if n <= 0 { + return fmt.Errorf("error decoding uint64 from validators binary encoding") + } + i += n + err = validators.AlterPower(*publicKey, power) + if err != nil { + return err + } + } + return nil +} diff --git a/blockchain/validators_test.go b/blockchain/validators_test.go new file mode 100644 index 0000000000000000000000000000000000000000..35513d43af9c052f3c31a494ec6d5ea4ee7c149b --- /dev/null +++ b/blockchain/validators_test.go @@ -0,0 +1,50 @@ +package blockchain + +import ( + "testing" + + "fmt" + + "math/rand" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidators_AlterPower(t *testing.T) { + vs := NewValidators() + pow1 := uint64(2312312321) + assert.NoError(t, vs.AlterPower(pubKey(1), pow1)) + assert.Equal(t, pow1, vs.TotalPower()) +} + +func TestValidators_Encode(t *testing.T) { + vs := NewValidators() + rnd := rand.New(rand.NewSource(43534543)) + for i := 0; i < 100; i++ { + power := uint64(rnd.Intn(10)) + require.NoError(t, vs.AlterPower(pubKey(rnd.Int63()), power)) + } + encoded := vs.Encode() + vsOut := NewValidators() + require.NoError(t, DecodeValidators(encoded, &vsOut)) + // Check decoded matches encoded + var publicKeyPower []interface{} + vs.Iterate(func(publicKey crypto.PublicKey, power uint64) (stop bool) { + publicKeyPower = append(publicKeyPower, publicKey, power) + return + }) + vsOut.Iterate(func(publicKey crypto.PublicKey, power uint64) (stop bool) { + assert.Equal(t, publicKeyPower[0], publicKey) + assert.Equal(t, publicKeyPower[1], power) + publicKeyPower = publicKeyPower[2:] + return + }) + assert.Len(t, publicKeyPower, 0, "should exhaust all validators in decoded multiset") +} + +func pubKey(secret interface{}) crypto.PublicKey { + return acm.NewConcreteAccountFromSecret(fmt.Sprintf("%v", secret)).PublicKey +} diff --git a/blockchain/validators_window.go b/blockchain/validators_window.go new file mode 100644 index 0000000000000000000000000000000000000000..11ebf209b36e441b3826785725abdd7975e485fa --- /dev/null +++ b/blockchain/validators_window.go @@ -0,0 +1,64 @@ +package blockchain + +import ( + "github.com/hyperledger/burrow/crypto" +) + +type ValidatorsWindow struct { + Buckets []Validators + Total Validators + head int +} + +// Provides a sliding window over the last size buckets of validator power changes +func NewValidatorsWindow(size int) ValidatorsWindow { + if size < 1 { + size = 1 + } + vw := ValidatorsWindow{ + Buckets: make([]Validators, size), + Total: NewValidators(), + } + vw.Buckets[vw.head] = NewValidators() + return vw +} + +// Updates the current head bucket (accumulator) +func (vw *ValidatorsWindow) AlterPower(publicKey crypto.PublicKey, power uint64) error { + return vw.Buckets[vw.head].AlterPower(publicKey, power) +} + +func (vw *ValidatorsWindow) CommitInto(validatorsToUpdate *Validators) error { + var err error + if vw.Buckets[vw.head].Iterate(func(publicKey crypto.PublicKey, power uint64) (stop bool) { + // Update the sink validators + err = validatorsToUpdate.AlterPower(publicKey, power) + if err != nil { + return true + } + // Add to total power + err = vw.Total.AddPower(publicKey, power) + if err != nil { + return true + } + return false + }) { + // If iteration stopped there was an error + return err + } + // move the ring buffer on + vw.head = (vw.head + 1) % len(vw.Buckets) + // Subtract the tail bucket (if any) from the total + if vw.Buckets[vw.head].Iterate(func(publicKey crypto.PublicKey, power uint64) (stop bool) { + err = vw.Total.SubtractPower(publicKey, power) + if err != nil { + return false + } + return true + }) { + return err + } + // Clear new head bucket (and possibly previous tail) + vw.Buckets[vw.head] = NewValidators() + return nil +} diff --git a/consensus/tendermint/abci/app.go b/consensus/tendermint/abci/app.go index 6ad441166a96d5dcaa9b572927fbace51314cbab..31306fca8a1ea388c65e745ec36e495dbb9dd8e0 100644 --- a/consensus/tendermint/abci/app.go +++ b/consensus/tendermint/abci/app.go @@ -7,13 +7,14 @@ import ( bcm "github.com/hyperledger/burrow/blockchain" "github.com/hyperledger/burrow/consensus/tendermint/codes" + "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/execution" "github.com/hyperledger/burrow/logging" "github.com/hyperledger/burrow/logging/structure" "github.com/hyperledger/burrow/project" "github.com/hyperledger/burrow/txs" "github.com/pkg/errors" - abci_types "github.com/tendermint/abci/types" + abciTypes "github.com/tendermint/abci/types" "github.com/tendermint/go-wire" ) @@ -21,21 +22,21 @@ const responseInfoName = "Burrow" type App struct { // State - blockchain bcm.MutableBlockchain + blockchain *bcm.Blockchain checker execution.BatchExecutor committer execution.BatchCommitter mempoolLocker sync.Locker // We need to cache these from BeginBlock for when we need actually need it in Commit - block *abci_types.RequestBeginBlock + block *abciTypes.RequestBeginBlock // Utility txDecoder txs.Decoder // Logging logger *logging.Logger } -var _ abci_types.Application = &App{} +var _ abciTypes.Application = &App{} -func NewApp(blockchain bcm.MutableBlockchain, +func NewApp(blockchain *bcm.Blockchain, checker execution.BatchExecutor, committer execution.BatchCommitter, logger *logging.Logger) *App { @@ -55,9 +56,9 @@ func (app *App) SetMempoolLocker(mempoolLocker sync.Locker) { app.mempoolLocker = mempoolLocker } -func (app *App) Info(info abci_types.RequestInfo) abci_types.ResponseInfo { - tip := app.blockchain.Tip() - return abci_types.ResponseInfo{ +func (app *App) Info(info abciTypes.RequestInfo) abciTypes.ResponseInfo { + tip := app.blockchain.Tip + return abciTypes.ResponseInfo{ Data: responseInfoName, Version: project.History.CurrentVersion().String(), LastBlockHeight: int64(tip.LastBlockHeight()), @@ -65,25 +66,25 @@ func (app *App) Info(info abci_types.RequestInfo) abci_types.ResponseInfo { } } -func (app *App) SetOption(option abci_types.RequestSetOption) (respSetOption abci_types.ResponseSetOption) { +func (app *App) SetOption(option abciTypes.RequestSetOption) (respSetOption abciTypes.ResponseSetOption) { respSetOption.Log = "SetOption not supported" respSetOption.Code = codes.UnsupportedRequestCode return } -func (app *App) Query(reqQuery abci_types.RequestQuery) (respQuery abci_types.ResponseQuery) { +func (app *App) Query(reqQuery abciTypes.RequestQuery) (respQuery abciTypes.ResponseQuery) { respQuery.Log = "Query not supported" respQuery.Code = codes.UnsupportedRequestCode return } -func (app *App) CheckTx(txBytes []byte) abci_types.ResponseCheckTx { +func (app *App) CheckTx(txBytes []byte) abciTypes.ResponseCheckTx { tx, err := app.txDecoder.DecodeTx(txBytes) if err != nil { app.logger.TraceMsg("CheckTx decoding error", "tag", "CheckTx", structure.ErrorKey, err) - return abci_types.ResponseCheckTx{ + return abciTypes.ResponseCheckTx{ Code: codes.EncodingErrorCode, Log: fmt.Sprintf("Encoding error: %s", err), } @@ -97,7 +98,7 @@ func (app *App) CheckTx(txBytes []byte) abci_types.ResponseCheckTx { "tag", "CheckTx", "tx_hash", receipt.TxHash, "creates_contract", receipt.CreatesContract) - return abci_types.ResponseCheckTx{ + return abciTypes.ResponseCheckTx{ Code: codes.EncodingErrorCode, Log: fmt.Sprintf("CheckTx could not execute transaction: %s, error: %v", tx, err), } @@ -108,30 +109,30 @@ func (app *App) CheckTx(txBytes []byte) abci_types.ResponseCheckTx { "tag", "CheckTx", "tx_hash", receipt.TxHash, "creates_contract", receipt.CreatesContract) - return abci_types.ResponseCheckTx{ + return abciTypes.ResponseCheckTx{ Code: codes.TxExecutionSuccessCode, Log: "CheckTx success - receipt in data", Data: receiptBytes, } } -func (app *App) InitChain(chain abci_types.RequestInitChain) (respInitChain abci_types.ResponseInitChain) { +func (app *App) InitChain(chain abciTypes.RequestInitChain) (respInitChain abciTypes.ResponseInitChain) { // Could verify agreement on initial validator set here return } -func (app *App) BeginBlock(block abci_types.RequestBeginBlock) (respBeginBlock abci_types.ResponseBeginBlock) { +func (app *App) BeginBlock(block abciTypes.RequestBeginBlock) (respBeginBlock abciTypes.ResponseBeginBlock) { app.block = &block return } -func (app *App) DeliverTx(txBytes []byte) abci_types.ResponseDeliverTx { +func (app *App) DeliverTx(txBytes []byte) abciTypes.ResponseDeliverTx { tx, err := app.txDecoder.DecodeTx(txBytes) if err != nil { app.logger.TraceMsg("DeliverTx decoding error", "tag", "DeliverTx", structure.ErrorKey, err) - return abci_types.ResponseDeliverTx{ + return abciTypes.ResponseDeliverTx{ Code: codes.EncodingErrorCode, Log: fmt.Sprintf("Encoding error: %s", err), } @@ -145,7 +146,7 @@ func (app *App) DeliverTx(txBytes []byte) abci_types.ResponseDeliverTx { "tag", "DeliverTx", "tx_hash", receipt.TxHash, "creates_contract", receipt.CreatesContract) - return abci_types.ResponseDeliverTx{ + return abciTypes.ResponseDeliverTx{ Code: codes.TxExecutionErrorCode, Log: fmt.Sprintf("DeliverTx could not execute transaction: %s, error: %s", tx, err), } @@ -156,20 +157,30 @@ func (app *App) DeliverTx(txBytes []byte) abci_types.ResponseDeliverTx { "tx_hash", receipt.TxHash, "creates_contract", receipt.CreatesContract) receiptBytes := wire.BinaryBytes(receipt) - return abci_types.ResponseDeliverTx{ + return abciTypes.ResponseDeliverTx{ Code: codes.TxExecutionSuccessCode, Log: "DeliverTx success - receipt in data", Data: receiptBytes, } } -func (app *App) EndBlock(reqEndBlock abci_types.RequestEndBlock) (respEndBlock abci_types.ResponseEndBlock) { +func (app *App) EndBlock(reqEndBlock abciTypes.RequestEndBlock) abciTypes.ResponseEndBlock { // Validator mutation goes here - return + var validatorUpdates abciTypes.Validators + app.blockchain.IterateValidators(func(publicKey crypto.PublicKey, power uint64) (stop bool) { + validatorUpdates = append(validatorUpdates, abciTypes.Validator{ + Address: publicKey.Address().Bytes(), + PubKey: publicKey.ABCIPubKey(), + Power: int64(power), + }) + return + }) + return abciTypes.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } } -func (app *App) Commit() abci_types.ResponseCommit { - tip := app.blockchain.Tip() +func (app *App) Commit() abciTypes.ResponseCommit { app.logger.InfoMsg("Committing block", "tag", "Commit", structure.ScopeKey, "Commit()", @@ -177,8 +188,8 @@ func (app *App) Commit() abci_types.ResponseCommit { "hash", app.block.Hash, "txs", app.block.Header.NumTxs, "block_time", app.block.Header.Time, // [CSK] this sends a fairly non-sensical number; should be human readable - "last_block_time", tip.LastBlockTime(), - "last_block_hash", tip.LastBlockHash()) + "last_block_time", app.blockchain.Tip.LastBlockTime(), + "last_block_hash", app.blockchain.Tip.LastBlockHash()) // Lock the checker while we reset it and possibly while recheckTxs replays transactions app.checker.Lock() @@ -233,7 +244,7 @@ func (app *App) Commit() abci_types.ResponseCommit { "but Tendermint reports a block height of %v, and the two should agree", app.blockchain.LastBlockHeight(), app.block.Header.Height)) } - return abci_types.ResponseCommit{ + return abciTypes.ResponseCommit{ Data: appHash, } } diff --git a/crypto/crypto.go b/crypto/crypto.go index da0e85dd0af80b0c12d026c551fd0b6ae8ed0389..fc8ebf9e16e89ec5393aed337d9fd4367c0c37a9 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -2,17 +2,8 @@ package crypto import ( "bytes" - crand "crypto/rand" - "crypto/sha256" - "encoding/json" "fmt" "io" - - "github.com/btcsuite/btcd/btcec" - tm_crypto "github.com/tendermint/go-crypto" - "github.com/tmthrgd/go-hex" - "golang.org/x/crypto/ed25519" - "golang.org/x/crypto/ripemd160" ) type CurveType int8 @@ -33,6 +24,11 @@ func (k CurveType) String() string { } } +// Get this CurveType's 8 bit identifier as a byte +func (k CurveType) Byte() byte { + return byte(k) +} + func CurveTypeFromString(s string) (CurveType, error) { switch s { case "secp256k1": @@ -45,17 +41,6 @@ func CurveTypeFromString(s string) (CurveType, error) { } } -func (p PublicKey) AddressHashType() string { - switch p.CurveType { - case CurveTypeEd25519: - return "go-crypto-0.5.0" - case CurveTypeSecp256k1: - return "btc" - default: - return "" - } -} - type ErrInvalidCurve string func (err ErrInvalidCurve) Error() string { @@ -69,117 +54,6 @@ type Signer interface { Sign(msg []byte) (Signature, error) } -// PublicKey -type PublicKey struct { - CurveType CurveType - PublicKey []byte -} - -type PrivateKey struct { - CurveType CurveType - PublicKey []byte - PrivateKey []byte -} - -type PublicKeyJSON struct { - CurveType string - PublicKey string -} - -func (p PublicKey) MarshalJSON() ([]byte, error) { - jStruct := PublicKeyJSON{ - CurveType: p.CurveType.String(), - PublicKey: hex.EncodeUpperToString(p.PublicKey), - } - txt, err := json.Marshal(jStruct) - return txt, err -} - -func (p PublicKey) MarshalText() ([]byte, error) { - return p.MarshalJSON() -} - -func (p *PublicKey) UnmarshalJSON(text []byte) error { - var jStruct PublicKeyJSON - err := json.Unmarshal(text, &jStruct) - if err != nil { - return err - } - CurveType, err := CurveTypeFromString(jStruct.CurveType) - if err != nil { - return err - } - bs, err := hex.DecodeString(jStruct.PublicKey) - if err != nil { - return err - } - p.CurveType = CurveType - p.PublicKey = bs - return nil -} - -func (p *PublicKey) UnmarshalText(text []byte) error { - return p.UnmarshalJSON(text) -} - -func (p PublicKey) IsValid() bool { - switch p.CurveType { - case CurveTypeEd25519: - return len(p.PublicKey) == ed25519.PublicKeySize - case CurveTypeSecp256k1: - return len(p.PublicKey) == btcec.PubKeyBytesLenCompressed - default: - return false - } -} -func (p PublicKey) Verify(msg []byte, signature Signature) bool { - switch p.CurveType { - case CurveTypeEd25519: - return ed25519.Verify(p.PublicKey, msg, signature.Signature[:]) - case CurveTypeSecp256k1: - pub, err := btcec.ParsePubKey(p.PublicKey, btcec.S256()) - if err != nil { - return false - } - sig, err := btcec.ParseDERSignature(signature.Signature, btcec.S256()) - if err != nil { - return false - } - return sig.Verify(msg, pub) - default: - panic(fmt.Sprintf("invalid curve type")) - } -} - -func (p PublicKey) Address() Address { - switch p.CurveType { - case CurveTypeEd25519: - // FIMXE: tendermint go-crypto-0.5.0 uses weird scheme, this is fixed in 0.6.0 - tmPubKey := new(tm_crypto.PubKeyEd25519) - copy(tmPubKey[:], p.PublicKey) - addr, _ := AddressFromBytes(tmPubKey.Address()) - return addr - case CurveTypeSecp256k1: - sha := sha256.New() - sha.Write(p.PublicKey[:]) - - hash := ripemd160.New() - hash.Write(sha.Sum(nil)) - addr, _ := AddressFromBytes(hash.Sum(nil)) - return addr - default: - panic(fmt.Sprintf("unknown CurveType %d", p.CurveType)) - } -} - -func (p PublicKey) RawBytes() []byte { - return p.PublicKey[:] -} - -func (p PublicKey) String() string { - return hex.EncodeUpperToString(p.PublicKey) -} - // Signable is an interface for all signable things. // It typically removes signatures before serializing. type Signable interface { @@ -195,167 +69,3 @@ func SignBytes(chainID string, o Signable) []byte { } return buf.Bytes() } - -// Currently this is a stub that reads the raw bytes returned by key_client and returns -// an ed25519 public key. -func PublicKeyFromBytes(bs []byte, curveType CurveType) (PublicKey, error) { - switch curveType { - case CurveTypeEd25519: - if len(bs) != ed25519.PublicKeySize { - return PublicKey{}, fmt.Errorf("bytes passed have length %v but ed25519 public keys have %v bytes", - len(bs), ed25519.PublicKeySize) - } - case CurveTypeSecp256k1: - if len(bs) != btcec.PubKeyBytesLenCompressed { - return PublicKey{}, fmt.Errorf("bytes passed have length %v but secp256k1 public keys have %v bytes", - len(bs), btcec.PubKeyBytesLenCompressed) - } - default: - return PublicKey{}, ErrInvalidCurve(curveType) - } - - return PublicKey{PublicKey: bs, CurveType: curveType}, nil -} - -func (p PrivateKey) RawBytes() []byte { - return p.PrivateKey -} - -func (p PrivateKey) Sign(msg []byte) (Signature, error) { - switch p.CurveType { - case CurveTypeEd25519: - if len(p.PrivateKey) != ed25519.PrivateKeySize { - return Signature{}, fmt.Errorf("bytes passed have length %v but ed25519 private keys have %v bytes", - len(p.PrivateKey), ed25519.PrivateKeySize) - } - privKey := ed25519.PrivateKey(p.PrivateKey) - return Signature{ed25519.Sign(privKey, msg)}, nil - case CurveTypeSecp256k1: - if len(p.PrivateKey) != btcec.PrivKeyBytesLen { - return Signature{}, fmt.Errorf("bytes passed have length %v but secp256k1 private keys have %v bytes", - len(p.PrivateKey), btcec.PrivKeyBytesLen) - } - privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), p.PrivateKey) - - sig, err := privKey.Sign(msg) - if err != nil { - return Signature{}, err - } - return Signature{Signature: sig.Serialize()}, nil - default: - return Signature{}, ErrInvalidCurve(p.CurveType) - } -} - -func (p PrivateKey) GetPublicKey() PublicKey { - return PublicKey{CurveType: p.CurveType, PublicKey: p.PublicKey} -} - -func PrivateKeyFromRawBytes(privKeyBytes []byte, curveType CurveType) (PrivateKey, error) { - switch curveType { - case CurveTypeEd25519: - if len(privKeyBytes) != ed25519.PrivateKeySize { - return PrivateKey{}, fmt.Errorf("bytes passed have length %v but ed25519 private keys have %v bytes", - len(privKeyBytes), ed25519.PrivateKeySize) - } - return PrivateKey{PrivateKey: privKeyBytes, PublicKey: privKeyBytes[32:], CurveType: CurveTypeEd25519}, nil - case CurveTypeSecp256k1: - if len(privKeyBytes) != btcec.PrivKeyBytesLen { - return PrivateKey{}, fmt.Errorf("bytes passed have length %v but secp256k1 private keys have %v bytes", - len(privKeyBytes), btcec.PrivKeyBytesLen) - } - privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes) - return PrivateKey{PrivateKey: privKey.Serialize(), PublicKey: pubKey.SerializeCompressed(), CurveType: CurveTypeSecp256k1}, nil - default: - return PrivateKey{}, ErrInvalidCurve(curveType) - } -} - -func GeneratePrivateKey(random io.Reader, curveType CurveType) (PrivateKey, error) { - if random == nil { - random = crand.Reader - } - switch curveType { - case CurveTypeEd25519: - _, priv, err := ed25519.GenerateKey(random) - if err != nil { - return PrivateKey{}, err - } - return PrivateKeyFromRawBytes(priv, CurveTypeEd25519) - case CurveTypeSecp256k1: - privKeyBytes := make([]byte, 32) - _, err := random.Read(privKeyBytes) - if err != nil { - return PrivateKey{}, err - } - return PrivateKeyFromRawBytes(privKeyBytes, CurveTypeSecp256k1) - default: - return PrivateKey{}, ErrInvalidCurve(curveType) - } -} - -func PrivateKeyFromSecret(secret string, curveType CurveType) PrivateKey { - hasher := sha256.New() - hasher.Write(([]byte)(secret)) - // No error from a buffer - privateKey, _ := GeneratePrivateKey(bytes.NewBuffer(hasher.Sum(nil)), curveType) - return privateKey -} - -// Ensures the last 32 bytes of the ed25519 private key is the public key derived from the first 32 private bytes -func EnsureEd25519PrivateKeyCorrect(candidatePrivateKey ed25519.PrivateKey) error { - if len(candidatePrivateKey) != ed25519.PrivateKeySize { - return fmt.Errorf("ed25519 key has size %v but %v bytes passed as key", ed25519.PrivateKeySize, - len(candidatePrivateKey)) - } - _, derivedPrivateKey, err := ed25519.GenerateKey(bytes.NewBuffer(candidatePrivateKey)) - if err != nil { - return err - } - if !bytes.Equal(derivedPrivateKey, candidatePrivateKey) { - return fmt.Errorf("ed25519 key generated from prefix of %X should equal %X, but is %X", - candidatePrivateKey, candidatePrivateKey, derivedPrivateKey) - } - return nil -} - -func ChainSign(signer Signer, chainID string, o Signable) (Signature, error) { - sig, err := signer.Sign(SignBytes(chainID, o)) - if err != nil { - return Signature{}, err - } - return sig, nil -} - -// Signature - -type Signature struct { - Signature []byte -} - -// Currently this is a stub that reads the raw bytes returned by key_client and returns -// an ed25519 signature. -func SignatureFromBytes(bs []byte, curveType CurveType) (Signature, error) { - switch curveType { - case CurveTypeEd25519: - signatureEd25519 := Signature{} - if len(bs) != ed25519.SignatureSize { - return Signature{}, fmt.Errorf("bytes passed have length %v by ed25519 signatures have %v bytes", - len(bs), ed25519.SignatureSize) - } - copy(signatureEd25519.Signature[:], bs) - return Signature{ - Signature: bs, - }, nil - case CurveTypeSecp256k1: - return Signature{ - Signature: bs, - }, nil - default: - return Signature{}, nil - } -} - -func (sig Signature) RawBytes() []byte { - return sig.Signature -} diff --git a/crypto/private_key.go b/crypto/private_key.go new file mode 100644 index 0000000000000000000000000000000000000000..f1bad18969e89de0d4882a293e6f2b0117a19709 --- /dev/null +++ b/crypto/private_key.go @@ -0,0 +1,141 @@ +package crypto + +import ( + "bytes" + cryptoRand "crypto/rand" + "crypto/sha256" + "fmt" + "io" + + "github.com/btcsuite/btcd/btcec" + "golang.org/x/crypto/ed25519" +) + +type PrivateKey struct { + CurveType CurveType + PublicKey []byte + PrivateKey []byte +} + +// Currently this is a stub that reads the raw bytes returned by key_client and returns +// an ed25519 public key. +func PublicKeyFromBytes(bs []byte, curveType CurveType) (PublicKey, error) { + switch curveType { + case CurveTypeEd25519: + if len(bs) != ed25519.PublicKeySize { + return PublicKey{}, fmt.Errorf("bytes passed have length %v but ed25519 public keys have %v bytes", + len(bs), ed25519.PublicKeySize) + } + case CurveTypeSecp256k1: + if len(bs) != btcec.PubKeyBytesLenCompressed { + return PublicKey{}, fmt.Errorf("bytes passed have length %v but secp256k1 public keys have %v bytes", + len(bs), btcec.PubKeyBytesLenCompressed) + } + default: + return PublicKey{}, ErrInvalidCurve(curveType) + } + + return PublicKey{PublicKey: bs, CurveType: curveType}, nil +} + +func (p PrivateKey) RawBytes() []byte { + return p.PrivateKey +} + +func (p PrivateKey) Sign(msg []byte) (Signature, error) { + switch p.CurveType { + case CurveTypeEd25519: + if len(p.PrivateKey) != ed25519.PrivateKeySize { + return Signature{}, fmt.Errorf("bytes passed have length %v but ed25519 private keys have %v bytes", + len(p.PrivateKey), ed25519.PrivateKeySize) + } + privKey := ed25519.PrivateKey(p.PrivateKey) + return Signature{ed25519.Sign(privKey, msg)}, nil + case CurveTypeSecp256k1: + if len(p.PrivateKey) != btcec.PrivKeyBytesLen { + return Signature{}, fmt.Errorf("bytes passed have length %v but secp256k1 private keys have %v bytes", + len(p.PrivateKey), btcec.PrivKeyBytesLen) + } + privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), p.PrivateKey) + + sig, err := privKey.Sign(msg) + if err != nil { + return Signature{}, err + } + return Signature{Signature: sig.Serialize()}, nil + default: + return Signature{}, ErrInvalidCurve(p.CurveType) + } +} + +func (p PrivateKey) GetPublicKey() PublicKey { + return PublicKey{CurveType: p.CurveType, PublicKey: p.PublicKey} +} + +func PrivateKeyFromRawBytes(privKeyBytes []byte, curveType CurveType) (PrivateKey, error) { + switch curveType { + case CurveTypeEd25519: + if len(privKeyBytes) != ed25519.PrivateKeySize { + return PrivateKey{}, fmt.Errorf("bytes passed have length %v but ed25519 private keys have %v bytes", + len(privKeyBytes), ed25519.PrivateKeySize) + } + return PrivateKey{PrivateKey: privKeyBytes, PublicKey: privKeyBytes[32:], CurveType: CurveTypeEd25519}, nil + case CurveTypeSecp256k1: + if len(privKeyBytes) != btcec.PrivKeyBytesLen { + return PrivateKey{}, fmt.Errorf("bytes passed have length %v but secp256k1 private keys have %v bytes", + len(privKeyBytes), btcec.PrivKeyBytesLen) + } + privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes) + return PrivateKey{PrivateKey: privKey.Serialize(), PublicKey: pubKey.SerializeCompressed(), CurveType: CurveTypeSecp256k1}, nil + default: + return PrivateKey{}, ErrInvalidCurve(curveType) + } +} + +func GeneratePrivateKey(random io.Reader, curveType CurveType) (PrivateKey, error) { + if random == nil { + random = cryptoRand.Reader + } + switch curveType { + case CurveTypeEd25519: + _, priv, err := ed25519.GenerateKey(random) + if err != nil { + return PrivateKey{}, err + } + return PrivateKeyFromRawBytes(priv, CurveTypeEd25519) + case CurveTypeSecp256k1: + privKeyBytes := make([]byte, 32) + _, err := random.Read(privKeyBytes) + if err != nil { + return PrivateKey{}, err + } + return PrivateKeyFromRawBytes(privKeyBytes, CurveTypeSecp256k1) + default: + return PrivateKey{}, ErrInvalidCurve(curveType) + } +} + +func PrivateKeyFromSecret(secret string, curveType CurveType) PrivateKey { + hasher := sha256.New() + hasher.Write(([]byte)(secret)) + // No error from a buffer + privateKey, _ := GeneratePrivateKey(bytes.NewBuffer(hasher.Sum(nil)), curveType) + return privateKey +} + +// Ensures the last 32 bytes of the ed25519 private key is the public key derived from the first 32 private bytes +func EnsureEd25519PrivateKeyCorrect(candidatePrivateKey ed25519.PrivateKey) error { + if len(candidatePrivateKey) != ed25519.PrivateKeySize { + return fmt.Errorf("ed25519 key has size %v but %v bytes passed as key", ed25519.PrivateKeySize, + len(candidatePrivateKey)) + } + _, derivedPrivateKey, err := ed25519.GenerateKey(bytes.NewBuffer(candidatePrivateKey)) + if err != nil { + return err + } + if !bytes.Equal(derivedPrivateKey, candidatePrivateKey) { + return fmt.Errorf("ed25519 key generated from prefix of %X should equal %X, but is %X", + candidatePrivateKey, candidatePrivateKey, derivedPrivateKey) + } + return nil +} diff --git a/crypto/public_key.go b/crypto/public_key.go new file mode 100644 index 0000000000000000000000000000000000000000..a3d6564c51a355bf8190e217a97d1bfbc8f79505 --- /dev/null +++ b/crypto/public_key.go @@ -0,0 +1,197 @@ +package crypto + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + + "github.com/btcsuite/btcd/btcec" + abci "github.com/tendermint/abci/types" + tmCrypto "github.com/tendermint/go-crypto" + "github.com/tmthrgd/go-hex" + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/ripemd160" +) + +// PublicKey +type PublicKey struct { + CurveType CurveType + PublicKey []byte + // memoised address + address *Address +} + +type PublicKeyJSON struct { + CurveType string + PublicKey string +} + +// Returns the length in bytes of the public key +func PublicKeyLength(curveType CurveType) int { + switch curveType { + case CurveTypeEd25519: + return ed25519.PublicKeySize + case CurveTypeSecp256k1: + return btcec.PubKeyBytesLenCompressed + default: + // Other functions rely on this + return 0 + } +} + +func (p PublicKey) MarshalJSON() ([]byte, error) { + jStruct := PublicKeyJSON{ + CurveType: p.CurveType.String(), + PublicKey: hex.EncodeUpperToString(p.PublicKey), + } + txt, err := json.Marshal(jStruct) + return txt, err +} + +func (p PublicKey) MarshalText() ([]byte, error) { + return p.MarshalJSON() +} + +func (p *PublicKey) UnmarshalJSON(text []byte) error { + var jStruct PublicKeyJSON + err := json.Unmarshal(text, &jStruct) + if err != nil { + return err + } + CurveType, err := CurveTypeFromString(jStruct.CurveType) + if err != nil { + return err + } + bs, err := hex.DecodeString(jStruct.PublicKey) + if err != nil { + return err + } + p.CurveType = CurveType + p.PublicKey = bs + return nil +} + +func (p *PublicKey) UnmarshalText(text []byte) error { + return p.UnmarshalJSON(text) +} + +func (p PublicKey) IsValid() bool { + return PublicKeyLength(p.CurveType) == len(p.PublicKey) +} + +func (p PublicKey) Verify(msg []byte, signature Signature) bool { + switch p.CurveType { + case CurveTypeEd25519: + return ed25519.Verify(p.PublicKey, msg, signature.Signature[:]) + case CurveTypeSecp256k1: + pub, err := btcec.ParsePubKey(p.PublicKey, btcec.S256()) + if err != nil { + return false + } + sig, err := btcec.ParseDERSignature(signature.Signature, btcec.S256()) + if err != nil { + return false + } + return sig.Verify(msg, pub) + default: + panic(fmt.Sprintf("invalid curve type")) + } +} + +func (p PublicKey) Address() Address { + if p.address == nil { + address := p.computeAddress() + p.address = &address + } + return *p.address +} + +func (p PublicKey) computeAddress() Address { + switch p.CurveType { + case CurveTypeEd25519: + // FIMXE: tendermint go-crypto-0.5.0 uses weird scheme, this is fixed in 0.6.0 + tmPubKey := new(tmCrypto.PubKeyEd25519) + copy(tmPubKey[:], p.PublicKey) + addr, _ := AddressFromBytes(tmPubKey.Address()) + return addr + case CurveTypeSecp256k1: + sha := sha256.New() + sha.Write(p.PublicKey[:]) + + hash := ripemd160.New() + hash.Write(sha.Sum(nil)) + addr, _ := AddressFromBytes(hash.Sum(nil)) + return addr + default: + panic(fmt.Sprintf("unknown CurveType %d", p.CurveType)) + } +} + +func (p PublicKey) AddressHashType() string { + switch p.CurveType { + case CurveTypeEd25519: + return "go-crypto-0.5.0" + case CurveTypeSecp256k1: + return "btc" + default: + return "" + } +} + +func (p PublicKey) RawBytes() []byte { + return p.PublicKey[:] +} + +// Return the ABCI PubKey. See Tendermint protobuf.go for the go-crypto conversion this is based on +func (p PublicKey) ABCIPubKey() abci.PubKey { + switch p.CurveType { + case CurveTypeEd25519: + return abci.PubKey{ + Type: "ed25519", + Data: p.RawBytes(), + } + case CurveTypeSecp256k1: + return abci.PubKey{ + Type: "secp256k1", + Data: p.RawBytes(), + } + default: + return abci.PubKey{} + } +} + +func (p PublicKey) String() string { + return hex.EncodeUpperToString(p.PublicKey) +} + +// Produces a binary encoding of the CurveType byte plus +// the public key for padded to a fixed width on the right +func (p PublicKey) Encode() []byte { + encoded := make([]byte, PublicKeyLength(p.CurveType)+1) + encoded[0] = p.CurveType.Byte() + copy(encoded[1:], p.PublicKey) + return encoded +} + +// Decodes an encoded public key returning the number of bytes consumed +func DecodePublicKeyFixedWidth(buf []byte, publicKey *PublicKey) (int, error) { + if len(buf) < 1 { + return 0, fmt.Errorf("encoded bytes buffer must not be empty") + } + curveType := CurveType(buf[0]) + publicKeyEnd := PublicKeyLength(curveType) + 1 + if publicKeyEnd <= 0 { + return 0, fmt.Errorf("CurveType with identifier %v is unknown", curveType.Byte()) + } + if len(buf) < publicKeyEnd { + return 0, fmt.Errorf("encoded bytes buffer has length %v but public key encoding for %v needs %v bytes", + len(buf), curveType, publicKeyEnd) + } + + publicKey.CurveType = curveType + publicKey.PublicKey = buf[1:publicKeyEnd] + if !publicKey.IsValid() { + return publicKeyEnd, fmt.Errorf("decoded public key %v is not valid", publicKey) + } + return publicKeyEnd, nil +} diff --git a/crypto/signature.go b/crypto/signature.go new file mode 100644 index 0000000000000000000000000000000000000000..fcbff9de8615a56443a0279bfbab4d04b9a9ac3c --- /dev/null +++ b/crypto/signature.go @@ -0,0 +1,46 @@ +package crypto + +import ( + "fmt" + + "golang.org/x/crypto/ed25519" +) + +type Signature struct { + Signature []byte +} + +// Currently this is a stub that reads the raw bytes returned by key_client and returns +// an ed25519 signature. +func SignatureFromBytes(bs []byte, curveType CurveType) (Signature, error) { + switch curveType { + case CurveTypeEd25519: + signatureEd25519 := Signature{} + if len(bs) != ed25519.SignatureSize { + return Signature{}, fmt.Errorf("bytes passed have length %v by ed25519 signatures have %v bytes", + len(bs), ed25519.SignatureSize) + } + copy(signatureEd25519.Signature[:], bs) + return Signature{ + Signature: bs, + }, nil + case CurveTypeSecp256k1: + return Signature{ + Signature: bs, + }, nil + default: + return Signature{}, nil + } +} + +func (sig Signature) RawBytes() []byte { + return sig.Signature +} + +func ChainSign(signer Signer, chainID string, o Signable) (Signature, error) { + sig, err := signer.Sign(SignBytes(chainID, o)) + if err != nil { + return Signature{}, err + } + return sig, nil +} diff --git a/genesis/spec/template_account.go b/genesis/spec/template_account.go new file mode 100644 index 0000000000000000000000000000000000000000..f09cd57a1b53b27895263ba3d8d0836b4310056d --- /dev/null +++ b/genesis/spec/template_account.go @@ -0,0 +1 @@ +package spec diff --git a/governance/governance.go b/governance/governance.go new file mode 100644 index 0000000000000000000000000000000000000000..b3e56f5b8307a645379c05d533e2eb64361700b7 --- /dev/null +++ b/governance/governance.go @@ -0,0 +1,17 @@ +// The governance package contains functionality for altering permissions, token distribution, consensus parameters, +// validators, and network forks. +package governance + +// TODO: +// - Set validator power +// - Set account amount(s) +// - Set account permissions +// - Set global permissions +// - Set ConsensusParams +// Future considerations: +// - Handle network forks/termination/merging/replacement ? +// - Provide transaction in stasis/sudo (voting?) +// - Handle bonding by other means (e.g. pre-shared key permitting n bondings) +// - Network administered proxies (i.e. instead of keys have password authentication for identities - allow calls to originate as if from address without key?) +// Subject to: +// - Less than 1/3 validator power change per block diff --git a/rpc/service.go b/rpc/service.go index 3a40c9cbdf4ce1a7c5c1fb5f6057ea81d920fede..0a360749541a9c542a2ed405914ae6431fe861c6 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -48,14 +48,14 @@ type Service struct { nameReg execution.NameRegIterable mempoolAccounts *execution.Accounts subscribable event.Subscribable - blockchain bcm.Blockchain + blockchain *bcm.Blockchain transactor *execution.Transactor nodeView *query.NodeView logger *logging.Logger } func NewService(ctx context.Context, state state.Iterable, nameReg execution.NameRegIterable, - checker state.Reader, subscribable event.Subscribable, blockchain bcm.Blockchain, keyClient keys.KeyClient, + checker state.Reader, subscribable event.Subscribable, blockchain *bcm.Blockchain, keyClient keys.KeyClient, transactor *execution.Transactor, nodeView *query.NodeView, logger *logging.Logger) *Service { return &Service{ @@ -151,7 +151,7 @@ func (s *Service) Unsubscribe(ctx context.Context, subscriptionID string) error } func (s *Service) Status() (*ResultStatus, error) { - tip := s.blockchain.Tip() + tip := s.blockchain.Tip latestHeight := tip.LastBlockHeight() var ( latestBlockMeta *tm_types.BlockMeta @@ -241,7 +241,7 @@ func (s *Service) ListAccounts(predicate func(acm.Account) bool) (*ResultListAcc }) return &ResultListAccounts{ - BlockHeight: s.blockchain.Tip().LastBlockHeight(), + BlockHeight: s.blockchain.Tip.LastBlockHeight(), Accounts: accounts, }, nil } @@ -353,7 +353,7 @@ func (s *Service) GetBlock(height uint64) (*ResultGetBlock, error) { // Passing 0 for maxHeight sets the upper height of the range to the current // blockchain height. func (s *Service) ListBlocks(minHeight, maxHeight uint64) (*ResultListBlocks, error) { - latestHeight := s.blockchain.Tip().LastBlockHeight() + latestHeight := s.blockchain.Tip.LastBlockHeight() if minHeight == 0 { minHeight = 1 @@ -378,15 +378,17 @@ func (s *Service) ListBlocks(minHeight, maxHeight uint64) (*ResultListBlocks, er } func (s *Service) ListValidators() (*ResultListValidators, error) { - // TODO: when we reintroduce support for bonding and unbonding update this - // to reflect the mutable bonding state - validators := s.blockchain.Validators() - concreteValidators := make([]*acm.ConcreteValidator, len(validators)) - for i, validator := range validators { - concreteValidators[i] = acm.AsConcreteValidator(validator) - } + concreteValidators := make([]*acm.ConcreteValidator, 0, s.blockchain.NumValidators()) + s.blockchain.IterateValidators(func(publicKey crypto.PublicKey, power uint64) (stop bool) { + concreteValidators = append(concreteValidators, &acm.ConcreteValidator{ + Address: publicKey.Address(), + PublicKey: publicKey, + Power: power, + }) + return + }) return &ResultListValidators{ - BlockHeight: s.blockchain.Tip().LastBlockHeight(), + BlockHeight: s.blockchain.Tip.LastBlockHeight(), BondedValidators: concreteValidators, UnbondingValidators: nil, }, nil diff --git a/txs/gov_tx.go b/txs/gov_tx.go new file mode 100644 index 0000000000000000000000000000000000000000..ebfdb6f49f05444c23e56278f9e087af2b788b61 --- /dev/null +++ b/txs/gov_tx.go @@ -0,0 +1,91 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/crypto" + "github.com/tendermint/go-wire" +) + +type CallTx struct { + Input *TxInput + // Pointer since CallTx defines unset 'to' address as inducing account creation + Address *crypto.Address + GasLimit uint64 + Fee uint64 + Data []byte + txHashMemoizer +} + +var _ Tx = &CallTx{} + +func NewCallTx(st state.AccountGetter, from crypto.PublicKey, to *crypto.Address, data []byte, + amt, gasLimit, fee uint64) (*CallTx, error) { + + addr := from.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from) + } + + sequence := acc.Sequence() + 1 + return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil +} + +func NewCallTxWithSequence(from crypto.PublicKey, to *crypto.Address, data []byte, + amt, gasLimit, fee, sequence uint64) *CallTx { + input := &TxInput{ + Address: from.Address(), + Amount: amt, + Sequence: sequence, + PublicKey: from, + } + + return &CallTx{ + Input: input, + Address: to, + GasLimit: gasLimit, + Fee: fee, + Data: data, + } +} + +func (tx *CallTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != 1 { + return fmt.Errorf("CallTx expects a single AddressableSigner for its single Input but %v were provieded", + len(signingAccounts)) + } + var err error + tx.Input.PublicKey = signingAccounts[0].PublicKey() + tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign %v: %v", tx, err) + } + return nil +} + +func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + wire.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *CallTx) GetInputs() []TxInput { + return []TxInput{*tx.Input} +} + +func (tx *CallTx) String() string { + return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data) +} + +func (tx *CallTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +}