diff --git a/state/facade.go b/state/facade.go index 7b66e646f..9e9254162 100644 --- a/state/facade.go +++ b/state/facade.go @@ -38,8 +38,8 @@ type Facade interface { PendingTx(id tx.ID) *tx.Tx AddPendingTx(trx *tx.Tx) error AddPendingTxAndBroadcast(trx *tx.Tx) error - StoredBlock(height uint32) *store.StoredBlock - StoredTx(id tx.ID) *store.StoredTx + StoredBlock(height uint32) *store.CommittedBlock + StoredTx(id tx.ID) *store.CommittedTx BlockHash(height uint32) hash.Hash BlockHeight(hash hash.Hash) uint32 AccountByAddress(addr crypto.Address) *account.Account diff --git a/state/mock.go b/state/mock.go index a3ad0bec9..48e1ebb2c 100644 --- a/state/mock.go +++ b/state/mock.go @@ -161,7 +161,7 @@ func (m *MockState) CommitteePower() int64 { return m.TestCommittee.TotalPower() } -func (m *MockState) StoredBlock(height uint32) *store.StoredBlock { +func (m *MockState) StoredBlock(height uint32) *store.CommittedBlock { m.lk.RLock() defer m.lk.RUnlock() @@ -169,7 +169,7 @@ func (m *MockState) StoredBlock(height uint32) *store.StoredBlock { return b } -func (m *MockState) StoredTx(id tx.ID) *store.StoredTx { +func (m *MockState) StoredTx(id tx.ID) *store.CommittedTx { m.lk.RLock() defer m.lk.RUnlock() diff --git a/state/state.go b/state/state.go index b9a9b18c3..f52931ed8 100644 --- a/state/state.go +++ b/state/state.go @@ -618,7 +618,7 @@ func (st *state) IsValidator(addr crypto.Address) bool { return st.store.HasValidator(addr) } -func (st *state) StoredBlock(height uint32) *store.StoredBlock { +func (st *state) StoredBlock(height uint32) *store.CommittedBlock { b, err := st.store.Block(height) if err != nil { st.logger.Trace("error on retrieving block", "err", err) @@ -627,7 +627,7 @@ func (st *state) StoredBlock(height uint32) *store.StoredBlock { return b } -func (st *state) StoredTx(id tx.ID) *store.StoredTx { +func (st *state) StoredTx(id tx.ID) *store.CommittedTx { tx, err := st.store.Transaction(id) if err != nil { st.logger.Trace("searching transaction in local store failed", "id", id, "err", err) diff --git a/store/block.go b/store/block.go index f062ce72a..81d755ab6 100644 --- a/store/block.go +++ b/store/block.go @@ -3,6 +3,7 @@ package store import ( "bytes" + "github.com/pactus-project/pactus/crypto" "github.com/pactus-project/pactus/crypto/hash" "github.com/pactus-project/pactus/types/block" "github.com/pactus-project/pactus/util" @@ -12,6 +13,9 @@ import ( ) func blockKey(height uint32) []byte { return append(blockPrefix, util.Uint32ToSlice(height)...) } +func publicKeyKey(addr crypto.Address) []byte { + return append(publicKeyPrefix, addr.Bytes()...) +} func blockHashKey(hash hash.Hash) []byte { return append(blockHeightPrefix, hash.Bytes()...) } @@ -62,11 +66,24 @@ func (bs *blockStore) saveBlock(batch *leveldb.Batch, height uint32, block *bloc regs[i].height = height regs[i].offset = uint32(offset) + pubKey := trx.PublicKey() + if pubKey != nil { + if !bs.hasPublicKey(trx.Payload().Signer()) { + publicKeyKey := publicKeyKey(trx.Payload().Signer()) + batch.Put(publicKeyKey, pubKey.Bytes()) + } else { + // we have indexed this public key, se we can remove it + trx.SetPublicKey(nil) + } + } + err := trx.Encode(w) if err != nil { panic(err) // Should we panic? } regs[i].length = uint32(w.Len() - offset) + + trx.SetPublicKey(pubKey) } blockKey := blockKey(height) blockHashKey := blockHashKey(blockHash) @@ -101,3 +118,11 @@ func (bs *blockStore) hasBlock(height uint32) bool { } return has } + +func (bs *blockStore) hasPublicKey(addr crypto.Address) bool { + has, err := bs.db.Has(publicKeyKey(addr), nil) + if err != nil { + return false + } + return has +} diff --git a/store/interface.go b/store/interface.go index 9af1a4c20..bfa17014e 100644 --- a/store/interface.go +++ b/store/interface.go @@ -1,6 +1,8 @@ package store import ( + "bytes" + "github.com/pactus-project/pactus/crypto" "github.com/pactus-project/pactus/crypto/hash" "github.com/pactus-project/pactus/types/account" @@ -16,13 +18,13 @@ import ( // TODO: How to undo or rollback at least for last 21 blocks -type StoredBlock struct { +type CommittedBlock struct { BlockHash hash.Hash Height uint32 Data []byte } -func (s *StoredBlock) ToBlock() *block.Block { +func (s *CommittedBlock) ToBlock() *block.Block { b, err := block.FromBytes(s.Data) if err != nil { panic(err) @@ -33,35 +35,42 @@ func (s *StoredBlock) ToBlock() *block.Block { return b } -type StoredTx struct { +type CommittedTx struct { TxID tx.ID Height uint32 BlockTime uint32 Data []byte } -func (s *StoredTx) ToTx() *tx.Tx { - trx, err := tx.FromBytes(s.Data) - if err != nil { +func (s *CommittedTx) ToTx() *tx.Tx { + trx := new(tx.Tx) + r := bytes.NewReader(s.Data) + if err := trx.Decode(r); err != nil { panic(err) } - if !trx.ID().EqualsTo(s.TxID) { - panic("invalid data. transaction id does not match") - } + + // TODO: we can set public key, if it doesn't set + // pubKey, found := store.PublicKey(trx.Payload().Signer()) + // if !found { + // panic("unable to find the public key") + // } + // trx.SetPublicKey(pubKey) + return trx } type Reader interface { - Block(height uint32) (*StoredBlock, error) + Block(height uint32) (*CommittedBlock, error) BlockHeight(hash hash.Hash) uint32 BlockHash(height uint32) hash.Hash RecentBlockByStamp(stamp hash.Stamp) (uint32, *block.Block) - Transaction(id tx.ID) (*StoredTx, error) + Transaction(id tx.ID) (*CommittedTx, error) + PublicKey(addr crypto.Address) (crypto.PublicKey, bool) HasAccount(crypto.Address) bool Account(addr crypto.Address) (*account.Account, error) AccountByNumber(number int32) (*account.Account, error) TotalAccounts() int32 - HasValidator(crypto.Address) bool + HasValidator(addr crypto.Address) bool ValidatorAddresses() []crypto.Address Validator(addr crypto.Address) (*validator.Validator, error) ValidatorByNumber(num int32) (*validator.Validator, error) diff --git a/store/mock.go b/store/mock.go index 2c9be551b..64c943e62 100644 --- a/store/mock.go +++ b/store/mock.go @@ -33,11 +33,11 @@ func MockingStore(ts *testsuite.TestSuite) *MockStore { } } -func (m *MockStore) Block(height uint32) (*StoredBlock, error) { +func (m *MockStore) Block(height uint32) (*CommittedBlock, error) { b, ok := m.Blocks[height] if ok { d, _ := b.Bytes() - return &StoredBlock{ + return &CommittedBlock{ BlockHash: b.Hash(), Height: height, Data: d, @@ -63,12 +63,23 @@ func (m *MockStore) BlockHeight(hash hash.Hash) uint32 { return 0 } -func (m *MockStore) Transaction(id tx.ID) (*StoredTx, error) { +func (m *MockStore) PublicKey(addr crypto.Address) (crypto.PublicKey, bool) { + for _, block := range m.Blocks { + for _, trx := range block.Transactions() { + if trx.Payload().Signer() == addr { + return trx.PublicKey(), true + } + } + } + return nil, false +} + +func (m *MockStore) Transaction(id tx.ID) (*CommittedTx, error) { for height, block := range m.Blocks { for _, trx := range block.Transactions() { if trx.ID() == id { d, _ := trx.Bytes() - return &StoredTx{ + return &CommittedTx{ TxID: id, Height: height, BlockTime: block.Header().UnixTime(), diff --git a/store/store.go b/store/store.go index 2bd7fe8ba..2ee461cc6 100644 --- a/store/store.go +++ b/store/store.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/pactus-project/pactus/crypto" + "github.com/pactus-project/pactus/crypto/bls" "github.com/pactus-project/pactus/crypto/hash" "github.com/pactus-project/pactus/types/account" "github.com/pactus-project/pactus/types/block" @@ -35,6 +36,7 @@ var ( accountPrefix = []byte{0x05} validatorPrefix = []byte{0x07} blockHeightPrefix = []byte{0x09} + publicKeyPrefix = []byte{0x0a} ) func tryGet(db *leveldb.DB, key []byte) ([]byte, error) { @@ -140,7 +142,7 @@ func (s *store) SaveBlock(height uint32, block *block.Block, cert *block.Certifi s.updateStampLookup(height, block) } -func (s *store) Block(height uint32) (*StoredBlock, error) { +func (s *store) Block(height uint32) (*CommittedBlock, error) { s.lk.Lock() defer s.lk.Unlock() @@ -154,7 +156,7 @@ func (s *store) Block(height uint32) (*StoredBlock, error) { return nil, err } - return &StoredBlock{ + return &CommittedBlock{ BlockHash: blockHash, Height: height, Data: data[hash.HashSize:], @@ -192,7 +194,20 @@ func (s *store) RecentBlockByStamp(stamp hash.Stamp) (uint32, *block.Block) { return 0, nil } -func (s *store) Transaction(id tx.ID) (*StoredTx, error) { +func (s *store) PublicKey(addr crypto.Address) (crypto.PublicKey, bool) { + bs, err := tryGet(s.db, publicKeyKey(addr)) + if err != nil { + return nil, false + } + pubKey, err := bls.PublicKeyFromBytes(bs) + if err != nil { + return nil, false + } + + return pubKey, true +} + +func (s *store) Transaction(id tx.ID) (*CommittedTx, error) { s.lk.Lock() defer s.lk.Unlock() @@ -211,7 +226,7 @@ func (s *store) Transaction(id tx.ID) (*StoredTx, error) { } blockTime := util.SliceToUint32(data[hash.HashSize+1 : hash.HashSize+5]) - return &StoredTx{ + return &CommittedTx{ TxID: id, Height: pos.height, BlockTime: blockTime, diff --git a/store/store_test.go b/store/store_test.go index ed821d6d6..dd135cc4f 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -161,3 +161,21 @@ func TestRecentBlockByStamp(t *testing.T) { assert.Zero(t, h) assert.Nil(t, b) } + +func TestIndexingPublicKeys(t *testing.T) { + td := setup(t) + + blkData, _ := td.store.Block(1) + blk := blkData.ToBlock() + for _, trx := range blk.Transactions() { + addr := trx.Payload().Signer() + pub, found := td.store.PublicKey(addr) + + assert.True(t, found) + assert.Equal(t, pub.Address(), addr) + } + + pub, found := td.store.PublicKey(td.RandomAddress()) + assert.False(t, found) + assert.Nil(t, pub) +} diff --git a/types/block/block_test.go b/types/block/block_test.go index 6a6bcb21e..a45d9c609 100644 --- a/types/block/block_test.go +++ b/types/block/block_test.go @@ -2,6 +2,7 @@ package block_test import ( "encoding/hex" + "fmt" "testing" "time" @@ -186,6 +187,7 @@ func TestBlockHash(t *testing.T) { txHashes := make([]hash.Hash, 0) for _, trx := range b.Transactions() { + fmt.Println(trx.ID().String()) txHashes = append(txHashes, trx.ID()) } txRoot := simplemerkle.NewTreeFromHashes(txHashes).Root() diff --git a/types/tx/tx.go b/types/tx/tx.go index 93d15012a..215cbff44 100644 --- a/types/tx/tx.go +++ b/types/tx/tx.go @@ -19,6 +19,8 @@ const ( flagLockTime = 0x80 ) +const decodingOptNoPublicKey = 0x01 + const maxMemoLength = 64 type ID = hash.Hash @@ -250,22 +252,38 @@ func (tx *Tx) UnmarshalCBOR(bs []byte) error { // SerializeSize returns the number of bytes it would take to serialize the transaction. func (tx *Tx) SerializeSize() int { - n := 150 + + n := 7 + encoding.VarIntSerializeSize(uint64(tx.Sequence())) + encoding.VarIntSerializeSize(uint64(tx.Fee())) + encoding.VarStringSerializeSize(tx.Memo()) if tx.Payload() != nil { n += tx.Payload().SerializeSize() } + if tx.data.Signature != nil { + n += bls.SignatureSize + } + if tx.data.PublicKey != nil { + n += bls.PublicKeySize + } return n } func (tx *Tx) Encode(w io.Writer) error { - err := tx.EncodeWithNoSignatory(w) + var decodingOpt uint8 + if tx.data.PublicKey == nil { + decodingOpt |= decodingOptNoPublicKey + } + + err := encoding.WriteElement(w, decodingOpt) if err != nil { return err } + err = tx.encodeWithNoSignatory(w) + if err != nil { + return err + } + if tx.data.Signature != nil { err = tx.data.Signature.Encode(w) if err != nil { @@ -282,7 +300,7 @@ func (tx *Tx) Encode(w io.Writer) error { return nil } -func (tx *Tx) EncodeWithNoSignatory(w io.Writer) error { +func (tx *Tx) encodeWithNoSignatory(w io.Writer) error { err := encoding.WriteElements(w, tx.data.Version, tx.data.Stamp) if err != nil { return err @@ -310,8 +328,14 @@ func (tx *Tx) EncodeWithNoSignatory(w io.Writer) error { return nil } -func (tx *Tx) DecodeWithNoSignatory(r io.Reader) error { - err := encoding.ReadElements(r, &tx.data.Version, &tx.data.Stamp) +func (tx *Tx) Decode(r io.Reader) error { + var decodingOpt uint8 + err := encoding.ReadElement(r, &decodingOpt) + if err != nil { + return err + } + + err = encoding.ReadElements(r, &tx.data.Version, &tx.data.Stamp) if err != nil { return err } @@ -358,14 +382,6 @@ func (tx *Tx) DecodeWithNoSignatory(r io.Reader) error { if err != nil { return err } - return nil -} - -func (tx *Tx) Decode(r io.Reader) error { - err := tx.DecodeWithNoSignatory(r) - if err != nil { - return err - } if !tx.IsSubsidyTx() { sig := new(bls.Signature) @@ -375,12 +391,14 @@ func (tx *Tx) Decode(r io.Reader) error { } tx.data.Signature = sig - pub := new(bls.PublicKey) - err = pub.Decode(r) - if err != nil { - return err + if decodingOpt&decodingOptNoPublicKey == 0 { + pub := new(bls.PublicKey) + err = pub.Decode(r) + if err != nil { + return err + } + tx.data.PublicKey = pub } - tx.data.PublicKey = pub } return nil @@ -395,7 +413,7 @@ func (tx *Tx) String() string { func (tx *Tx) SignBytes() []byte { buf := bytes.Buffer{} - err := tx.EncodeWithNoSignatory(&buf) + err := tx.encodeWithNoSignatory(&buf) if err != nil { return nil } diff --git a/types/tx/tx_test.go b/types/tx/tx_test.go index 4932dbede..2f8c512b6 100644 --- a/types/tx/tx_test.go +++ b/types/tx/tx_test.go @@ -164,12 +164,39 @@ func TestBasicCheck(t *testing.T) { }) t.Run("Invalid version", func(t *testing.T) { - d := ts.DecodingHex("023513630b1a00010001703db2cca1f0deb29fb42b98bd9d12971b1160168094ebdc0300") + d := ts.DecodingHex( + "00" + // flags + "02" + // version + "a1b2c3d4" + // stamp + "01" + // sequence + "01" + // fee + "01" + // payload type + "00" + // sender (treasury) + "012222222222222222222222222222222222222222" + // receiver + "01" + // amount + "00") // memo trx, err := tx.FromBytes(d) assert.NoError(t, err) err = trx.BasicCheck() assert.Equal(t, errors.Code(err), errors.ErrInvalidTx) }) + + t.Run("Invalid payload", func(t *testing.T) { + d := ts.DecodingHex( + "00" + // flags + "01" + // version + "a1b2c3d4" + // stamp + "01" + // sequence + "01" + // fee + "06" + // payload type + "00" + // sender (treasury) + "012222222222222222222222222222222222222222" + // receiver + "01" + // amount + "00") // memo + + _, err := tx.FromBytes(d) + assert.Error(t, err) + }) } func TestInvalidFee(t *testing.T) { @@ -302,17 +329,42 @@ func TestInvalidSignature(t *testing.T) { func TestSignBytes(t *testing.T) { d, _ := hex.DecodeString( - "01f10c077fcc04f5ef819fc9d6080101d3e45d249a39d806a1faec2fd85820db340b98e30168fc72a1a961933e694439b2e3c8751d27de5a" + - "d3b9c3dc91b9c9b59b010c746573742073656e642d7478b53d79e156e9417e010fa21f2b2a96bee6be46fcd233295d2f697cdb9e782b6112" + - "ac01c80d0d9d64c2320664c77fa2a68d82fa4fcac04a3b565267685e90db1b01420285d2f8295683c138c092c209479983ba159137077884" + - "6681b7b558e0611776208c0718006311c84b4a113335c70d1f5c7c5dd93a5625c4af51c48847abd0b590c055306162d2a03ca1cbf7bcc1") - h, _ := hash.FromString("2a04aef409194ff72e942346525428f6c030e2875be27205cb2ce46065ec543f") + "00" + // flags + "01" + // version + "a1b2c3d4" + // stamp + "01" + // sequence + "01" + // fee + "01" + // payload type + "013333333333333333333333333333333333333333" + // sender + "012222222222222222222222222222222222222222" + // receiver + "01" + // amount + "00" + // memo + "b53d79e156e9417e010fa21f2b2a96bee6be46fcd233295d2f697cdb9e782b6112ac01c80d0d9d64c2320664c77fa2a6" + // sig + "8d82fa4fcac04a3b565267685e90db1b01420285d2f8295683c138c092c209479983ba1591370778846681b7b558e061" + // pub key + "1776208c0718006311c84b4a113335c70d1f5c7c5dd93a5625c4af51c48847abd0b590c055306162d2a03ca1cbf7bcc1") // pub key + + h, _ := hash.FromString("33ad1b0533269ac4a3c919886065d0dcaf425945167d2e90ad965332445661b4") trx, err := tx.FromBytes(d) assert.NoError(t, err) assert.Equal(t, trx.SerializeSize(), len(d)) - sb := d[:len(d)-bls.PublicKeySize-bls.SignatureSize] + sb := d[1 : len(d)-bls.PublicKeySize-bls.SignatureSize] assert.Equal(t, sb, trx.SignBytes()) assert.Equal(t, trx.ID(), h) assert.Equal(t, trx.ID(), hash.CalcHash(sb)) } + +func TestNoPublicKey(t *testing.T) { + ts := testsuite.NewTestSuite(t) + + trx1, _ := ts.GenerateTestTransferTx() + trx1.SetPublicKey(nil) + bs1, _ := trx1.Bytes() + + trx2, _ := tx.FromBytes(bs1) + bs2, _ := trx2.Bytes() + + assert.Equal(t, bs1, bs2) + assert.Equal(t, trx1.ID(), trx2.ID()) + assert.Nil(t, trx2.PublicKey()) +} diff --git a/www/grpc/blockchain.go b/www/grpc/blockchain.go index b434e1172..8532d0f77 100644 --- a/www/grpc/blockchain.go +++ b/www/grpc/blockchain.go @@ -102,7 +102,7 @@ func (s *blockchainServer) GetBlock(_ context.Context, height := req.GetHeight() storedBlock := s.state.StoredBlock(height) if storedBlock == nil { - return nil, status.Errorf(codes.InvalidArgument, "block not found") + return nil, status.Errorf(codes.NotFound, "block not found") } res := &pactus.GetBlockResponse{ Height: storedBlock.Height, @@ -169,7 +169,7 @@ func (s *blockchainServer) GetAccount(_ context.Context, } acc := s.state.AccountByAddress(addr) if acc == nil { - return nil, status.Errorf(codes.InvalidArgument, "account not found") + return nil, status.Errorf(codes.NotFound, "account not found") } res := &pactus.GetAccountResponse{ Account: accountToProto(acc),