aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/block_processor_test.go5
-rw-r--r--core/chain_manager.go27
-rw-r--r--core/chain_manager_test.go9
-rw-r--r--core/genesis.go4
-rw-r--r--core/transaction_pool.go2
-rw-r--r--core/transaction_pool_test.go12
-rw-r--r--core/types/block.go99
-rw-r--r--core/types/block_test.go57
-rw-r--r--core/types/derive_sha.go4
-rw-r--r--core/types/transaction.go55
10 files changed, 180 insertions, 94 deletions
diff --git a/core/block_processor_test.go b/core/block_processor_test.go
index ad29404e1..64add7e8b 100644
--- a/core/block_processor_test.go
+++ b/core/block_processor_test.go
@@ -4,6 +4,7 @@ import (
"math/big"
"testing"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/pow/ezp"
@@ -19,7 +20,7 @@ func proc() (*BlockProcessor, *ChainManager) {
func TestNumber(t *testing.T) {
bp, chain := proc()
- block1 := chain.NewBlock(nil)
+ block1 := chain.NewBlock(common.Address{})
block1.Header().Number = big.NewInt(3)
err := bp.ValidateHeader(block1.Header(), chain.Genesis().Header())
@@ -27,7 +28,7 @@ func TestNumber(t *testing.T) {
t.Errorf("expected block number error")
}
- block1 = chain.NewBlock(nil)
+ block1 = chain.NewBlock(common.Address{})
err = bp.ValidateHeader(block1.Header(), chain.Genesis().Header())
if err == BlockNumberErr {
t.Errorf("didn't expect block number error")
diff --git a/core/chain_manager.go b/core/chain_manager.go
index 5316a3423..060805428 100644
--- a/core/chain_manager.go
+++ b/core/chain_manager.go
@@ -3,6 +3,7 @@ package core
import (
"bytes"
"fmt"
+ "io"
"math/big"
"sync"
@@ -254,22 +255,20 @@ func (bc *ChainManager) ResetWithGenesisBlock(gb *types.Block) {
bc.currentBlock = bc.genesisBlock
}
-func (self *ChainManager) Export() []byte {
+// Export writes the active chain to the given writer.
+func (self *ChainManager) Export(w io.Writer) error {
self.mu.RLock()
defer self.mu.RUnlock()
-
chainlogger.Infof("exporting %v blocks...\n", self.currentBlock.Header().Number)
-
- blocks := make([]*types.Block, int(self.currentBlock.NumberU64())+1)
for block := self.currentBlock; block != nil; block = self.GetBlock(block.Header().ParentHash) {
- blocks[block.NumberU64()] = block
+ if err := block.EncodeRLP(w); err != nil {
+ return err
+ }
}
-
- return common.Encode(blocks)
+ return nil
}
func (bc *ChainManager) insert(block *types.Block) {
- //encodedBlock := common.Encode(block)
bc.blockDb.Put([]byte("LastBlock"), block.Hash().Bytes())
bc.currentBlock = block
bc.lastBlockHash = block.Hash()
@@ -279,10 +278,9 @@ func (bc *ChainManager) insert(block *types.Block) {
}
func (bc *ChainManager) write(block *types.Block) {
- encodedBlock := common.Encode(block.RlpDataForStorage())
-
+ enc, _ := rlp.EncodeToBytes((*types.StorageBlock)(block))
key := append(blockHashPre, block.Hash().Bytes()...)
- bc.blockDb.Put(key, encodedBlock)
+ bc.blockDb.Put(key, enc)
}
// Accessors
@@ -324,13 +322,12 @@ func (self *ChainManager) GetBlock(hash common.Hash) *types.Block {
if len(data) == 0 {
return nil
}
- var block types.Block
+ var block types.StorageBlock
if err := rlp.Decode(bytes.NewReader(data), &block); err != nil {
- fmt.Println(err)
+ chainlogger.Errorf("invalid block RLP for hash %x: %v", hash, err)
return nil
}
-
- return &block
+ return (*types.Block)(&block)
}
func (self *ChainManager) GetBlockByNumber(num uint64) *types.Block {
diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go
index 898d37f9c..e49e594a3 100644
--- a/core/chain_manager_test.go
+++ b/core/chain_manager_test.go
@@ -1,7 +1,6 @@
package core
import (
- "bytes"
"fmt"
"math/big"
"os"
@@ -35,7 +34,7 @@ func testFork(t *testing.T, bman *BlockProcessor, i, N int, f func(td1, td2 *big
// asert the bmans have the same block at i
bi1 := bman.bc.GetBlockByNumber(uint64(i)).Hash()
bi2 := bman2.bc.GetBlockByNumber(uint64(i)).Hash()
- if bytes.Compare(bi1, bi2) != 0 {
+ if bi1 != bi2 {
t.Fatal("chains do not have the same hash at height", i)
}
@@ -270,11 +269,11 @@ func TestChainInsertions(t *testing.T) {
<-done
}
- if bytes.Equal(chain2[len(chain2)-1].Hash(), chainMan.CurrentBlock().Hash()) {
+ if chain2[len(chain2)-1].Hash() != chainMan.CurrentBlock().Hash() {
t.Error("chain2 is canonical and shouldn't be")
}
- if !bytes.Equal(chain1[len(chain1)-1].Hash(), chainMan.CurrentBlock().Hash()) {
+ if chain1[len(chain1)-1].Hash() != chainMan.CurrentBlock().Hash() {
t.Error("chain1 isn't canonical and should be")
}
}
@@ -320,7 +319,7 @@ func TestChainMultipleInsertions(t *testing.T) {
<-done
}
- if !bytes.Equal(chains[longest][len(chains[longest])-1].Hash(), chainMan.CurrentBlock().Hash()) {
+ if chains[longest][len(chains[longest])-1].Hash() != chainMan.CurrentBlock().Hash() {
t.Error("Invalid canonical chain")
}
}
diff --git a/core/genesis.go b/core/genesis.go
index 70845b502..3e00533ae 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -8,7 +8,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/state"
)
@@ -19,9 +18,6 @@ import (
var ZeroHash256 = make([]byte, 32)
var ZeroHash160 = make([]byte, 20)
var ZeroHash512 = make([]byte, 64)
-var EmptyShaList = crypto.Sha3(common.Encode([]interface{}{}))
-var EmptyListRoot = crypto.Sha3(common.Encode(""))
-
var GenesisDiff = big.NewInt(131072)
var GenesisGasLimit = big.NewInt(3141592)
diff --git a/core/transaction_pool.go b/core/transaction_pool.go
index 7c50e5e53..f2fe5c748 100644
--- a/core/transaction_pool.go
+++ b/core/transaction_pool.go
@@ -63,7 +63,7 @@ func NewTxPool(eventMux *event.TypeMux) *TxPool {
func (pool *TxPool) ValidateTransaction(tx *types.Transaction) error {
// Validate sender
if _, err := tx.From(); err != nil {
- return err
+ return ErrInvalidSender
}
// Validate curve param
v, _, _ := tx.Curve()
diff --git a/core/transaction_pool_test.go b/core/transaction_pool_test.go
index 418cb0415..bf9012573 100644
--- a/core/transaction_pool_test.go
+++ b/core/transaction_pool_test.go
@@ -4,10 +4,10 @@ import (
"crypto/ecdsa"
"testing"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/state"
)
@@ -21,11 +21,11 @@ func SQ() stateQuery {
}
func (self stateQuery) GetAccount(addr []byte) *state.StateObject {
- return state.NewStateObject(addr, self.db)
+ return state.NewStateObject(common.BytesToAddress(addr), self.db)
}
func transaction() *types.Transaction {
- return types.NewTransactionMessage(make([]byte, 20), common.Big0, common.Big0, common.Big0, nil)
+ return types.NewTransactionMessage(common.Address{}, common.Big0, common.Big0, common.Big0, nil)
}
func setup() (*TxPool, *ecdsa.PrivateKey) {
@@ -88,10 +88,8 @@ func TestRemoveInvalid(t *testing.T) {
func TestInvalidSender(t *testing.T) {
pool, _ := setup()
- tx := new(types.Transaction)
- tx.V = 28
- err := pool.ValidateTransaction(tx)
+ err := pool.ValidateTransaction(new(types.Transaction))
if err != ErrInvalidSender {
- t.Error("expected %v, got %v", ErrInvalidSender, err)
+ t.Errorf("expected %v, got %v", ErrInvalidSender, err)
}
}
diff --git a/core/types/block.go b/core/types/block.go
index aca23aa04..6f26358fb 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -3,12 +3,13 @@ package types
import (
"encoding/binary"
"fmt"
+ "io"
"math/big"
"sort"
"time"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -45,6 +46,14 @@ type Header struct {
Nonce [8]byte
}
+func (self *Header) Hash() common.Hash {
+ return rlpHash(self.rlpData(true))
+}
+
+func (self *Header) HashNoNonce() common.Hash {
+ return rlpHash(self.rlpData(false))
+}
+
func (self *Header) rlpData(withNonce bool) []interface{} {
fields := []interface{}{
self.ParentHash,
@@ -64,7 +73,6 @@ func (self *Header) rlpData(withNonce bool) []interface{} {
if withNonce {
fields = append(fields, self.MixDigest, self.Nonce)
}
-
return fields
}
@@ -72,12 +80,11 @@ func (self *Header) RlpData() interface{} {
return self.rlpData(true)
}
-func (self *Header) Hash() common.Hash {
- return common.BytesToHash(crypto.Sha3(common.Encode(self.rlpData(true))))
-}
-
-func (self *Header) HashNoNonce() common.Hash {
- return common.BytesToHash(crypto.Sha3(common.Encode(self.rlpData(false))))
+func rlpHash(x interface{}) (h common.Hash) {
+ hw := sha3.NewKeccak256()
+ rlp.Encode(hw, x)
+ hw.Sum(h[:0])
+ return h
}
type Block struct {
@@ -95,6 +102,26 @@ type Block struct {
Reward *big.Int
}
+// StorageBlock defines the RLP encoding of a Block stored in the
+// state database. The StorageBlock encoding contains fields that
+// would otherwise need to be recomputed.
+type StorageBlock Block
+
+// "external" block encoding. used for eth protocol, etc.
+type extblock struct {
+ Header *Header
+ Txs []*Transaction
+ Uncles []*Header
+}
+
+// "storage" block encoding. used for database.
+type storageblock struct {
+ Header *Header
+ Txs []*Transaction
+ Uncles []*Header
+ TD *big.Int
+}
+
func NewBlock(parentHash common.Hash, coinbase common.Address, root common.Hash, difficulty *big.Int, nonce uint64, extra string) *Block {
header := &Header{
Root: root,
@@ -107,9 +134,7 @@ func NewBlock(parentHash common.Hash, coinbase common.Address, root common.Hash,
GasLimit: new(big.Int),
}
header.SetNonce(nonce)
-
block := &Block{header: header, Reward: new(big.Int)}
-
return block
}
@@ -122,22 +147,40 @@ func NewBlockWithHeader(header *Header) *Block {
}
func (self *Block) DecodeRLP(s *rlp.Stream) error {
- var extblock struct {
- Header *Header
- Txs []*Transaction
- Uncles []*Header
- TD *big.Int // optional
+ var eb extblock
+ if err := s.Decode(&eb); err != nil {
+ return err
}
- if err := s.Decode(&extblock); err != nil {
+ self.header, self.uncles, self.transactions = eb.Header, eb.Uncles, eb.Txs
+ return nil
+}
+
+func (self Block) EncodeRLP(w io.Writer) error {
+ return rlp.Encode(w, extblock{
+ Header: self.header,
+ Txs: self.transactions,
+ Uncles: self.uncles,
+ })
+}
+
+func (self *StorageBlock) DecodeRLP(s *rlp.Stream) error {
+ var sb storageblock
+ if err := s.Decode(&sb); err != nil {
return err
}
- self.header = extblock.Header
- self.uncles = extblock.Uncles
- self.transactions = extblock.Txs
- self.Td = extblock.TD
+ self.header, self.uncles, self.transactions, self.Td = sb.Header, sb.Uncles, sb.Txs, sb.TD
return nil
}
+func (self StorageBlock) EncodeRLP(w io.Writer) error {
+ return rlp.Encode(w, storageblock{
+ Header: self.header,
+ Txs: self.transactions,
+ Uncles: self.uncles,
+ TD: self.Td,
+ })
+}
+
func (self *Block) Header() *Header {
return self.header
}
@@ -148,7 +191,7 @@ func (self *Block) Uncles() []*Header {
func (self *Block) SetUncles(uncleHeaders []*Header) {
self.uncles = uncleHeaders
- self.header.UncleHash = common.BytesToHash(crypto.Sha3(common.Encode(uncleHeaders)))
+ self.header.UncleHash = rlpHash(uncleHeaders)
}
func (self *Block) Transactions() Transactions {
@@ -213,7 +256,6 @@ func (self *Block) GasLimit() *big.Int { return self.header.GasLimit }
func (self *Block) GasUsed() *big.Int { return self.header.GasUsed }
func (self *Block) Root() common.Hash { return self.header.Root }
func (self *Block) SetRoot(root common.Hash) { self.header.Root = root }
-func (self *Block) Size() common.StorageSize { return common.StorageSize(len(common.Encode(self))) }
func (self *Block) GetTransaction(i int) *Transaction {
if len(self.transactions) > i {
return self.transactions[i]
@@ -227,6 +269,19 @@ func (self *Block) GetUncle(i int) *Header {
return nil
}
+func (self *Block) Size() common.StorageSize {
+ c := writeCounter(0)
+ rlp.Encode(&c, self)
+ return common.StorageSize(c)
+}
+
+type writeCounter common.StorageSize
+
+func (c *writeCounter) Write(b []byte) (int, error) {
+ *c += writeCounter(len(b))
+ return len(b), nil
+}
+
// Implement pow.Block
func (self *Block) Difficulty() *big.Int { return self.header.Difficulty }
func (self *Block) HashNoNonce() common.Hash { return self.header.HashNoNonce() }
diff --git a/core/types/block_test.go b/core/types/block_test.go
index 16ba3043f..e4f6c38a5 100644
--- a/core/types/block_test.go
+++ b/core/types/block_test.go
@@ -1,19 +1,60 @@
package types
import (
+ "bytes"
"math/big"
+ "reflect"
"testing"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/rlp"
)
-// XXX Tests doesn't really do anything. This tests exists while working on the fixed size conversions
-func TestConversion(t *testing.T) {
- var (
- parent common.Hash
- coinbase common.Address
- hash common.Hash
- )
+// from bcValidBlockTest.json, "SimpleTx"
+func TestBlockEncoding(t *testing.T) {
+ blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0")
- NewBlock(parent, coinbase, hash, big.NewInt(0), 0, "")
+ var block Block
+ if err := rlp.DecodeBytes(blockEnc, &block); err != nil {
+ t.Fatal("decode error: ", err)
+ }
+
+ check := func(f string, got, want interface{}) {
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("%s mismatch: got %v, want %v", f, got, want)
+ }
+ }
+ check("Difficulty", block.Difficulty(), big.NewInt(131072))
+ check("GasLimit", block.GasLimit(), big.NewInt(3141592))
+ check("GasUsed", block.GasUsed(), big.NewInt(21000))
+ check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1"))
+ check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
+ check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
+ check("Hash", block.Hash(), common.HexToHash("0a5843ac1cb04865017cb35a57b50b07084e5fcee39b5acadade33149f4fff9e"))
+ check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
+ check("Time", block.Time(), int64(1426516743))
+ check("Size", block.Size(), common.StorageSize(len(blockEnc)))
+
+ to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87")
+ check("Transactions", block.Transactions(), Transactions{
+ {
+ Payload: []byte{},
+ Amount: big.NewInt(10),
+ Price: big.NewInt(10),
+ GasLimit: big.NewInt(50000),
+ AccountNonce: 0,
+ V: 27,
+ R: common.FromHex("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f"),
+ S: common.FromHex("8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1"),
+ Recipient: &to,
+ },
+ })
+
+ ourBlockEnc, err := rlp.EncodeToBytes(&block)
+ if err != nil {
+ t.Fatal("encode error: ", err)
+ }
+ if !bytes.Equal(ourBlockEnc, blockEnc) {
+ t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc)
+ }
}
diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go
index 0fc45a508..10e3d7446 100644
--- a/core/types/derive_sha.go
+++ b/core/types/derive_sha.go
@@ -3,6 +3,7 @@ package types
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
@@ -15,7 +16,8 @@ func DeriveSha(list DerivableList) common.Hash {
db, _ := ethdb.NewMemDatabase()
trie := trie.New(nil, db)
for i := 0; i < list.Len(); i++ {
- trie.Update(common.Encode(i), list.GetRlp(i))
+ key, _ := rlp.EncodeToBytes(i)
+ trie.Update(key, list.GetRlp(i))
}
return common.BytesToHash(trie.Root())
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 24035a3ae..391fb46f5 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -1,16 +1,14 @@
package types
import (
- "bytes"
+ "crypto/ecdsa"
"errors"
"fmt"
- "io"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
- "github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -38,16 +36,18 @@ func NewTransactionMessage(to common.Address, amount, gasAmount, gasPrice *big.I
}
func NewTransactionFromBytes(data []byte) *Transaction {
+ // TODO: remove this function if possible. callers would
+ // much better off decoding into transaction directly.
+ // it's not that hard.
tx := new(Transaction)
- rlp.Decode(bytes.NewReader(data), tx)
+ rlp.DecodeBytes(data, tx)
return tx
}
-func (tx *Transaction) Hash() (a common.Hash) {
- h := sha3.NewKeccak256()
- rlp.Encode(h, []interface{}{tx.AccountNonce, tx.Price, tx.GasLimit, tx.Recipient, tx.Amount, tx.Payload})
- h.Sum(a[:0])
- return a
+func (tx *Transaction) Hash() common.Hash {
+ return rlpHash([]interface{}{
+ tx.AccountNonce, tx.Price, tx.GasLimit, tx.Recipient, tx.Amount, tx.Payload,
+ })
}
func (self *Transaction) Data() []byte {
@@ -122,17 +122,14 @@ func (tx *Transaction) SetSignatureValues(sig []byte) error {
return nil
}
-func (tx Transaction) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, []interface{}{
- tx.AccountNonce,
- tx.Price, tx.GasLimit,
- tx.Recipient,
- tx.Amount,
- tx.Payload,
- tx.V,
- tx.R,
- tx.S,
- })
+func (tx *Transaction) SignECDSA(prv *ecdsa.PrivateKey) error {
+ h := tx.Hash()
+ sig, err := crypto.Sign(h[:], prv)
+ if err != nil {
+ return err
+ }
+ tx.SetSignatureValues(sig)
+ return nil
}
// TODO: remove
@@ -141,11 +138,6 @@ func (tx *Transaction) RlpData() interface{} {
return append(data, tx.V, new(big.Int).SetBytes(tx.R).Bytes(), new(big.Int).SetBytes(tx.S).Bytes())
}
-// TODO: remove
-func (tx *Transaction) RlpEncode() []byte {
- return common.Encode(tx)
-}
-
func (tx *Transaction) String() string {
var from, to string
if f, err := tx.From(); err != nil {
@@ -158,6 +150,7 @@ func (tx *Transaction) String() string {
} else {
to = fmt.Sprintf("%x", t[:])
}
+ enc, _ := rlp.EncodeToBytes(tx)
return fmt.Sprintf(`
TX(%x)
Contract: %v
@@ -185,7 +178,7 @@ func (tx *Transaction) String() string {
tx.V,
tx.R,
tx.S,
- common.Encode(tx),
+ enc,
)
}
@@ -204,9 +197,13 @@ func (self Transactions) RlpData() interface{} {
return enc
}
-func (s Transactions) Len() int { return len(s) }
-func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s Transactions) GetRlp(i int) []byte { return common.Rlp(s[i]) }
+func (s Transactions) Len() int { return len(s) }
+func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+func (s Transactions) GetRlp(i int) []byte {
+ enc, _ := rlp.EncodeToBytes(s[i])
+ return enc
+}
type TxByNonce struct{ Transactions }