From 867869a73aad7edcef99db2ce65db4aa0691f67b Mon Sep 17 00:00:00 2001 From: icodezjb Date: Thu, 26 Oct 2017 19:40:12 +0800 Subject: [PATCH] Support spend transaction with account or utxo --- blockchain/account/accounts.go | 74 +++++------ blockchain/account/indexer.go | 20 +-- blockchain/account/reserve.go | 173 ++++++++++++++----------- blockchain/asset/asset.go | 2 +- blockchain/pin/pin.go | 2 +- blockchain/query.go | 56 ++++---- blockchain/signers/idgenerate.go | 2 +- blockchain/transact.go | 4 +- blockchain/txdb/snapshot.go | 6 +- blockchain/txdb/store.go | 6 +- cmd/bytomcli/example/issue.go | 114 ++++++++++------- cmd/bytomcli/example/spend.go | 268 +++++++++++++++++++++++++++++---------- protocol/block.go | 3 +- sync/idempotency/group.go | 75 +++++++++++ sync/idempotency/group_test.go | 85 +++++++++++++ 15 files changed, 610 insertions(+), 280 deletions(-) create mode 100644 sync/idempotency/group.go create mode 100644 sync/idempotency/group_test.go diff --git a/blockchain/account/accounts.go b/blockchain/account/accounts.go index e466b908..8541db1a 100644 --- a/blockchain/account/accounts.go +++ b/blockchain/account/accounts.go @@ -2,22 +2,22 @@ package account import ( + "context" + "encoding/json" "fmt" "sync" "time" - "context" - "encoding/json" - "github.com/bytom/log" - "github.com/bytom/errors" - "github.com/bytom/protocol" "github.com/bytom/blockchain/pin" - "github.com/golang/groupcache/lru" - "github.com/bytom/crypto/sha3pool" - "github.com/bytom/protocol/vm/vmutil" "github.com/bytom/blockchain/signers" "github.com/bytom/blockchain/txbuilder" "github.com/bytom/crypto/ed25519/chainkd" + "github.com/bytom/crypto/sha3pool" + "github.com/bytom/errors" + "github.com/bytom/log" + "github.com/bytom/protocol" + "github.com/bytom/protocol/vm/vmutil" + "github.com/golang/groupcache/lru" dbm "github.com/tendermint/tmlibs/db" ) @@ -29,11 +29,11 @@ var ( ErrBadIdentifier = errors.New("either ID or alias must be specified, and not both") ) -func NewManager(db dbm.DB, chain *protocol.Chain , pinStore *pin.Store) *Manager { +func NewManager(db dbm.DB, chain *protocol.Chain, pinStore *pin.Store) *Manager { return &Manager{ - db: db, - chain: chain, - utxoDB: newReserver(db, chain), + db: db, + chain: chain, + utxoDB: newReserver(db, chain, pinStore), pinStore: pinStore, cache: lru.New(maxAccountCache), aliasCache: lru.New(maxAccountCache), @@ -43,10 +43,10 @@ func NewManager(db dbm.DB, chain *protocol.Chain , pinStore *pin.Store) *Manager // Manager stores accounts and their associated control programs. type Manager struct { - db dbm.DB - chain *protocol.Chain - utxoDB *reserver - indexer Saver + db dbm.DB + chain *protocol.Chain + utxoDB *reserver + indexer Saver pinStore *pin.Store cacheMu sync.Mutex @@ -91,9 +91,9 @@ type Account struct { // Create creates a new Account. func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string, tags map[string]interface{}, clientToken string) (*Account, error) { - //if ret := m.db.Get([]byte(alias));ret != nil { - //return nil,errors.New("alias already exists") - //} + if ret := m.db.Get(json.RawMessage("ali" + alias)); len(ret) != 0 { + return nil, errors.New(fmt.Sprintf("alias:%s already exists", alias)) + } accountSigner, err := signers.Create(ctx, m.db, "account", xpubs, quorum, clientToken) if err != nil { @@ -134,20 +134,20 @@ func (m *Manager) UpdateTags(ctx context.Context, id, alias *string, tags map[st var key_id []byte if alias != nil { - key_id = m.db.Get([]byte(*alias)) + key_id = m.db.Get([]byte("ali" + *alias)) } else { key_id = json.RawMessage(*id) } bytes := m.db.Get(key_id) - if bytes == nil { - return errors.New("no exit this account.") + if len(bytes) == 0 { + return errors.New("no exit this account") } var account Account err := json.Unmarshal(bytes, &account) if err != nil { - return errors.New("this account can't be unmarshal.") + return errors.New("this account can't be unmarshal") } for k, v := range tags { @@ -195,7 +195,7 @@ func (m *Manager) FindByAlias(ctx context.Context, alias string) (*signers.Signe if err != nil { return nil, errors.Wrap(err) }*/ - bytez := m.db.Get([]byte(fmt.Sprintf("alias_account:%v", alias))) + bytez := m.db.Get([]byte("ali" + alias)) accountID = string(bytez[:]) m.cacheMu.Lock() m.aliasCache.Add(alias, accountID) @@ -214,17 +214,16 @@ func (m *Manager) findByID(ctx context.Context, id string) (*signers.Signer, err } bytes := m.db.Get(json.RawMessage(id)) - if bytes == nil { - return nil,errors.New("not find this account.") + if len(bytes) == 0 { + return nil, errors.New("not find this account.") } var account Account err := json.Unmarshal(bytes, &account) if err != nil { - return nil,errors.New("failed unmarshal this account.") + return nil, errors.New("failed unmarshal this account.") } - m.cacheMu.Lock() m.cache.Add(id, account.Signer) m.cacheMu.Unlock() @@ -293,17 +292,18 @@ func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*con var b32 [32]byte for _, p := range progs { - acp, err := json.Marshal(&struct{ + acp, err := json.Marshal(&struct { AccountID string KeyIndex uint64 ControlProgram []byte Change bool - ExpiresAt time.Time}{ - AccountID: p.accountID, - KeyIndex: p.keyIndex, - ControlProgram: p.controlProgram, - Change: p.change, - ExpiresAt: p.expiresAt}) + ExpiresAt time.Time + }{ + AccountID: p.accountID, + KeyIndex: p.keyIndex, + ControlProgram: p.controlProgram, + Change: p.change, + ExpiresAt: p.expiresAt}) if err != nil { return errors.Wrap(err, "failed marshal controlProgram") @@ -324,9 +324,9 @@ func (m *Manager) nextIndex(ctx context.Context) (uint64, error) { if m.acpIndexNext >= m.acpIndexCap { const incrby = 10000 // start 1,increments by 10,000 - if(m.acpIndexCap <= incrby){ + if m.acpIndexCap <= incrby { m.acpIndexCap = incrby + 1 - }else{ + } else { m.acpIndexCap += incrby } m.acpIndexNext = m.acpIndexCap - incrby diff --git a/blockchain/account/indexer.go b/blockchain/account/indexer.go index 94808351..58676907 100644 --- a/blockchain/account/indexer.go +++ b/blockchain/account/indexer.go @@ -30,13 +30,13 @@ const ( type AccountUTXOs struct { OutputID []byte AssetID []byte - Amount int64 + Amount uint64 AccountID string - CpIndex int64 + CpIndex uint64 Program []byte - Confirmed int64 + InBlock uint64 SourceID []byte - SourcePos int64 + SourcePos uint64 RefData []byte Change bool } @@ -200,7 +200,7 @@ func (m *Manager) loadAccountInfo(ctx context.Context, outs []*rawOutput) []*acc for s := range outsByScript { sha3pool.Sum256(b32[:], []byte(s)) bytes := m.db.Get(json.RawMessage("acp" + string(b32[:]))) - if bytes == nil { + if len(bytes) == 0 { continue } @@ -211,7 +211,7 @@ func (m *Manager) loadAccountInfo(ctx context.Context, outs []*rawOutput) []*acc //filte the accounts which exists in accountdb with wallet enabled isExist := m.db.Get(json.RawMessage(cp.AccountID)) - if isExist == nil { + if len(isExist) == 0 { continue } @@ -241,13 +241,13 @@ func (m *Manager) upsertConfirmedAccountOutputs(ctx context.Context, for _, out := range outs { au = &AccountUTXOs{OutputID: out.OutputID.Bytes(), AssetID: out.AssetId.Bytes(), - Amount: int64(out.Amount), + Amount: out.Amount, AccountID: out.AccountID, - CpIndex: int64(out.keyIndex), + CpIndex: out.keyIndex, Program: out.ControlProgram, - Confirmed: int64(block.Height), + InBlock: block.Height, SourceID: out.sourceID.Bytes(), - SourcePos: int64(out.sourcePos), + SourcePos: out.sourcePos, RefData: out.refData.Bytes(), Change: out.change} diff --git a/blockchain/account/reserve.go b/blockchain/account/reserve.go index f9271067..55e91fc0 100644 --- a/blockchain/account/reserve.go +++ b/blockchain/account/reserve.go @@ -1,21 +1,22 @@ package account import ( + "bytes" "context" - // "database/sql" + "encoding/json" "fmt" "sync" "sync/atomic" "time" - // "chain/core/pin" - //"github.com/blockchain/database/pg" + "github.com/bytom/blockchain/pin" "github.com/bytom/consensus" "github.com/bytom/errors" "github.com/bytom/protocol" "github.com/bytom/protocol/bc" "github.com/bytom/protocol/bc/legacy" - //"github.com/blockchain/sync/idempotency" + "github.com/bytom/sync/idempotency" + dbm "github.com/tendermint/tmlibs/db" ) @@ -74,11 +75,11 @@ type reservation struct { ClientToken *string } -func newReserver(db dbm.DB, c *protocol.Chain /*pinStore *pin.Store*/) *reserver { +func newReserver(db dbm.DB, c *protocol.Chain, pinStore *pin.Store) *reserver { return &reserver{ - c: c, - db: db, - //pinStore: pinStore, + c: c, + db: pinStore.DB, + pinStore: pinStore, reservations: make(map[uint64]*reservation), sources: make(map[source]*sourceReserver), } @@ -95,11 +96,11 @@ func newReserver(db dbm.DB, c *protocol.Chain /*pinStore *pin.Store*/) *reserver // reserver ensures idempotency of reservations until the reservation // expiration. type reserver struct { - c *protocol.Chain - db dbm.DB - //pinStore *pin.Store + c *protocol.Chain + db dbm.DB + pinStore *pin.Store nextReservationID uint64 - // idempotency idempotency.Group + idempotency idempotency.Group reservationsMu sync.Mutex reservations map[uint64]*reservation @@ -111,17 +112,15 @@ type reserver struct { // Reserve selects and reserves UTXOs according to the criteria provided // in source. The resulting reservation expires at exp. func (re *reserver) Reserve(ctx context.Context, src source, amount uint64, clientToken *string, exp time.Time) (*reservation, error) { - /* - if clientToken == nil { - return re.reserve(ctx, src, amount, clientToken, exp) - } - untypedRes, err := re.idempotency.Once(*clientToken, func() (interface{}, error) { - return re.reserve(ctx, src, amount, clientToken, exp) - }) - return untypedRes.(*reservation), err - */ - return re.reserve(ctx, src, amount, clientToken, exp) + if clientToken == nil { + return re.reserve(ctx, src, amount, clientToken, exp) + } + + untypedRes, err := re.idempotency.Once(*clientToken, func() (interface{}, error) { + return re.reserve(ctx, src, amount, clientToken, exp) + }) + return untypedRes.(*reservation), err } func (re *reserver) reserve(ctx context.Context, src source, amount uint64, clientToken *string, exp time.Time) (res *reservation, err error) { @@ -157,17 +156,14 @@ func (re *reserver) reserve(ctx context.Context, src source, amount uint64, clie // ReserveUTXO reserves a specific utxo for spending. The resulting // reservation expires at exp. func (re *reserver) ReserveUTXO(ctx context.Context, out bc.Hash, clientToken *string, exp time.Time) (*reservation, error) { - /* - if clientToken == nil { - return re.reserveUTXO(ctx, out, exp, nil) - } + if clientToken == nil { + return re.reserveUTXO(ctx, out, exp, nil) + } - untypedRes, err := re.idempotency.Once(*clientToken, func() (interface{}, error) { - return re.reserveUTXO(ctx, out, exp, clientToken) - }) - return untypedRes.(*reservation), err - */ - return re.reserveUTXO(ctx, out, exp, nil) + untypedRes, err := re.idempotency.Once(*clientToken, func() (interface{}, error) { + return re.reserveUTXO(ctx, out, exp, clientToken) + }) + return untypedRes.(*reservation), err } func (re *reserver) reserveUTXO(ctx context.Context, out bc.Hash, exp time.Time, clientToken *string) (*reservation, error) { @@ -265,8 +261,7 @@ func (re *reserver) source(src source) *sourceReserver { src: src, validFn: re.checkUTXO, heightFn: func() uint64 { - //return re.pinStore.Height(PinName) - return 0 + return re.pinStore.Height(PinName) }, cached: make(map[bc.Hash]*utxo), reserved: make(map[bc.Hash]uint64), @@ -398,63 +393,87 @@ func (sr *sourceReserver) refillCache(ctx context.Context) error { } func findMatchingUTXOs(ctx context.Context, db dbm.DB, src source, height uint64) ([]*utxo, error) { - /*const q = ` - SELECT output_id, amount, control_program_index, control_program, - source_id, source_pos, ref_data_hash - FROM account_utxos - WHERE account_id = $1 AND asset_id = $2 AND confirmed_in > $3 - `*/ + var utxos []*utxo - /*err := pg.ForQueryRows(ctx, db, q, src.AccountID, src.AssetID, height, - func(oid bc.Hash, amount uint64, cpIndex uint64, controlProg []byte, sourceID bc.Hash, sourcePos uint64, refData bc.Hash) { + var au AccountUTXOs + var b32 [3][32]byte + + iter := db.Iterator() + for iter.Next() { + key := string(iter.Key()) + if key[:3] != "acu" { + continue + } + + err := json.Unmarshal(iter.Value(), &au) + if err != nil { + return nil, errors.Wrap(err) + } + + if (au.AccountID == src.AccountID) && + (bytes.Equal(au.AssetID, src.AssetID.Bytes())) && + (au.InBlock > height) { + + copy(b32[0][:], au.OutputID) + copy(b32[1][:], au.SourceID) + copy(b32[2][:], au.RefData) + utxos = append(utxos, &utxo{ - OutputID: oid, - SourceID: sourceID, + OutputID: bc.NewHash(b32[0]), + SourceID: bc.NewHash(b32[1]), AssetID: src.AssetID, - Amount: amount, - SourcePos: sourcePos, - ControlProgram: controlProg, - RefDataHash: refData, + Amount: au.Amount, + SourcePos: au.SourcePos, + ControlProgram: au.Program, + RefDataHash: bc.NewHash(b32[2]), AccountID: src.AccountID, - ControlProgramIndex: cpIndex, + ControlProgramIndex: au.CpIndex, }) - }) - if err != nil { - return nil, errors.Wrap(err) - }*/ + + } + + } + + if len(utxos) == 0 { + return nil, errors.New("can't match utxo") + } + return utxos, nil } func findSpecificUTXO(ctx context.Context, db dbm.DB, outHash bc.Hash) (*utxo, error) { - /*const q = ` - SELECT account_id, asset_id, amount, control_program_index, control_program, - source_id, source_pos, ref_data_hash - FROM account_utxos - WHERE output_id = $1 - `*/ + u := new(utxo) + au := new(AccountUTXOs) + b32 := new([4][32]byte) + + accUTXOValue := db.Get(json.RawMessage("acu" + string(outHash.Bytes()))) + if len(accUTXOValue) != 0 { + err := json.Unmarshal(accUTXOValue, &au) + if err != nil { + return nil, errors.Wrap(err) + } - // TODO(oleg): maybe we need to scan txid:index too from here... - /*err := db.QueryRowContext(ctx, q, out).Scan( - &u.AccountID, - &u.AssetID, - &u.Amount, - &u.ControlProgramIndex, - &u.ControlProgram, - &u.SourceID, - &u.SourcePos, - &u.RefDataHash, - ) - if err == sql.ErrNoRows { - return nil, pg.ErrUserInputNotFound - } else if err != nil { - return nil, errors.Wrap(err) + copy(b32[0][:], au.OutputID) + copy(b32[1][:], au.AssetID) + copy(b32[2][:], au.SourceID) + copy(b32[3][:], au.RefData) + + u.OutputID = bc.NewHash(b32[0]) + u.AccountID = au.AccountID + u.AssetID = bc.NewAssetID(b32[1]) + u.Amount = au.Amount + u.ControlProgramIndex = au.CpIndex + u.ControlProgram = au.Program + u.SourceID = bc.NewHash(b32[2]) + u.SourcePos = au.SourcePos + u.RefDataHash = bc.NewHash(b32[3]) + + return u, nil } - u.OutputID = out - */ if outHash.String() != "73d1e97c7bcf2b084f936a40f4f2a72e909417f2b46699e8659fa4c4feddb98d" { - return u, nil + return u, errors.New(fmt.Sprintf("can't find utxo: %s", outHash.String())) } genesisBlock := &legacy.Block{ diff --git a/blockchain/asset/asset.go b/blockchain/asset/asset.go index 49df5a7f..3d430dd7 100644 --- a/blockchain/asset/asset.go +++ b/blockchain/asset/asset.go @@ -226,7 +226,7 @@ func (reg *Registry) findByID(ctx context.Context, id bc.AssetID) (*Asset, error } bytes := reg.db.Get([]byte(id.String())) - if bytes == nil { + if len(bytes) == 0 { return nil, errors.New("no exit this asset.") } var asset Asset diff --git a/blockchain/pin/pin.go b/blockchain/pin/pin.go index f2a4f4bc..a9876124 100644 --- a/blockchain/pin/pin.go +++ b/blockchain/pin/pin.go @@ -235,7 +235,7 @@ func (p *pin) complete(ctx context.Context, height uint64) error { ) bytes := p.db.Get(json.RawMessage("blp" + p.name)) - if bytes != nil { + if len(bytes) != 0 { err = json.Unmarshal(bytes, &block_processor) if err == nil && block_processor.Height >= max { goto Noupdate diff --git a/blockchain/query.go b/blockchain/query.go index e0b94822..de6aad86 100644 --- a/blockchain/query.go +++ b/blockchain/query.go @@ -22,7 +22,7 @@ var ( { "OutputID":"%x","AssetID":"%x","Amount":"%d", "AccountID":"%s","CpIndex":"%d","Program":"%x", - "Confirmed":"%d","SourceID":"%x","SourcePos":"%d", + "InBlock":"%d","SourceID":"%x","SourcePos":"%d", "RefData":"%x","Change":"%t" }` ) @@ -76,34 +76,44 @@ func (bcr *BlockchainReactor) listBalances(ctx context.Context, in requestQuery) type assetAmount struct { AssetID string - Amount int64 + Amount uint64 } + var ( - aa = assetAmount{} - accBalances = make(map[string][]assetAmount, 0) - accBalancesSort = make(map[string][]assetAmount, 0) - keys = make([]string, 0) - response = make([]interface{}, 0) + aaTmp = assetAmount{} + accBalances = make(map[string][]assetAmount) + keys = make([]string, 0) + response = make([]string, 0) + exist = false + index = 0 ) - accoutUTXOs := bcr.GetAccountUTXOs() + accountUTXOs := bcr.GetAccountUTXOs() - for _, res := range accoutUTXOs { + for _, res := range accountUTXOs { - aa.AssetID = fmt.Sprintf("%x", res.AssetID) - aa.Amount = res.Amount + aaTmp.AssetID = fmt.Sprintf("%x", res.AssetID) + aaTmp.Amount = res.Amount if _, ok := accBalances[res.AccountID]; ok { - for _, amentry := range accBalances[res.AccountID] { - if amentry.AssetID == aa.AssetID { - amentry.Amount += aa.Amount - } else { - accBalances[res.AccountID] = append(accBalances[res.AccountID], aa) + + for i, aA := range accBalances[res.AccountID] { + if aA.AssetID == aaTmp.AssetID { + exist = true + index = i + break } } + + if exist { + accBalances[res.AccountID][index].Amount += aaTmp.Amount + exist = false + } else { + accBalances[res.AccountID] = append(accBalances[res.AccountID], aaTmp) + } + } else { - accBalances[res.AccountID] = append(accBalances[res.AccountID], aa) + accBalances[res.AccountID] = append(accBalances[res.AccountID], aaTmp) } - } for k := range accBalances { @@ -113,11 +123,9 @@ func (bcr *BlockchainReactor) listBalances(ctx context.Context, in requestQuery) sort.Strings(keys) for _, k := range keys { - accBalancesSort[k] = accBalances[k] - } - - if len(accBalancesSort) != 0 { - response = append(response, accBalancesSort) + balanceString, _ := json.Marshal(accBalances[k]) + accBalancesString := fmt.Sprintf(`{"AccountID":"%s","Balances":"%s"}`, k, balanceString) + response = append(response, accBalancesString) } return response @@ -216,7 +224,7 @@ func (bcr *BlockchainReactor) listUnspentOutputs(ctx context.Context, in request restring = fmt.Sprintf(AccountUTXOFmt, res.OutputID, res.AssetID, res.Amount, res.AccountID, res.CpIndex, res.Program, - res.Confirmed, res.SourceID, res.SourcePos, + res.InBlock, res.SourceID, res.SourcePos, res.RefData, res.Change) response = append(response, restring) diff --git a/blockchain/signers/idgenerate.go b/blockchain/signers/idgenerate.go index 11505b79..c9d8e943 100644 --- a/blockchain/signers/idgenerate.go +++ b/blockchain/signers/idgenerate.go @@ -8,7 +8,7 @@ import ( ) //1 } diff --git a/cmd/bytomcli/example/spend.go b/cmd/bytomcli/example/spend.go index fd3fb706..26978a89 100644 --- a/cmd/bytomcli/example/spend.go +++ b/cmd/bytomcli/example/spend.go @@ -2,101 +2,229 @@ package example import ( "context" - stdjson "encoding/json" "fmt" + "os" + "time" - bchain "github.com/bytom/blockchain" - "github.com/bytom/blockchain/query" "github.com/bytom/blockchain/rpc" "github.com/bytom/blockchain/txbuilder" - "github.com/bytom/crypto/ed25519/chainkd" + "github.com/bytom/encoding/json" + + stdjson "encoding/json" + bchain "github.com/bytom/blockchain" + "strconv" ) +type accUTXOShort struct { + OutputID string `json:"OutputID"` + AccountID string `json:"AccountID"` + AssetID string `json:"AssetID"` + Amount string `json:"Amount"` +} + +type requestQuery struct { + Filter string `json:"filter,omitempty"` + FilterParams []interface{} `json:"filter_params,omitempty"` + SumBy []string `json:"sum_by,omitempty"` + PageSize int `json:"page_size"` + AscLongPoll bool `json:"ascending_with_long_poll,omitempty"` + Timeout json.Duration `json:"timeout"` + After string `json:"after"` + StartTimeMS uint64 `json:"start_time,omitempty"` + EndTimeMS uint64 `json:"end_time,omitempty"` + TimestampMS uint64 `json:"timestamp,omitempty"` + Type string `json:"type"` + Aliases []string `json:"aliases,omitempty"` +} + func SpendTest(client *rpc.Client, args []string) { // Create Account. fmt.Printf("To create Account:\n") - xprv, _ := chainkd.NewXPrv(nil) - xpub := xprv.XPub() - fmt.Printf("xprv_account:%v\n", xprv) - fmt.Printf("xpub_account:%v\n", xpub) - type Ins struct { - RootXPubs []chainkd.XPub `json:"root_xpubs"` - Quorum int - Alias string - Tags map[string]interface{} - ClientToken string `json:"client_token"` - } - var ins Ins - ins.RootXPubs = []chainkd.XPub{xpub} - ins.Quorum = 1 - ins.Alias = "alice" - ins.Tags = map[string]interface{}{"test_tag": "v0"} - ins.ClientToken = "account" - account := make([]query.AnnotatedAccount, 1) - client.Call(context.Background(), "/create-account", &[]Ins{ins}, &account) - fmt.Printf("account:%v\n", account) + aliceIns, xprvAlice := NewInstance("alice", Account) + bobIns, _ := NewInstance("bob", Account) + accounts, _ := NewAnnotate(client, Account, aliceIns, bobIns) // Create Asset. fmt.Printf("To create Asset:\n") - xprv_asset, _ := chainkd.NewXPrv(nil) - xpub_asset := xprv_asset.XPub() - fmt.Printf("xprv_asset:%v\n", xprv_asset) - fmt.Printf("xpub_asset:%v\n", xpub_asset) - type Ins_asset struct { - RootXPubs []chainkd.XPub `json:"root_xpubs"` - Quorum int - Alias string - Tags map[string]interface{} - Definition map[string]interface{} - ClientToken string `json:"client_token"` - } - var ins_asset Ins_asset - ins_asset.RootXPubs = []chainkd.XPub{xpub_asset} - ins_asset.Quorum = 1 - ins_asset.Alias = "gold" - ins_asset.Tags = map[string]interface{}{"test_tag": "v0"} - ins_asset.Definition = map[string]interface{}{"test_definition": "v0"} - ins_asset.ClientToken = "asset" - asset := make([]query.AnnotatedAsset, 1) - client.Call(context.Background(), "/create-asset", &[]Ins_asset{ins_asset}, &asset) - fmt.Printf("asset:%v\n", asset) - - // Build Transaction. + goldIns, xprvGold := NewInstance("gold", Asset) + _, assets := NewAnnotate(client, Asset, goldIns) + + // Build Transaction1-Issue fmt.Printf("To build transaction:\n") - // Now spend actions buildReqFmt := ` {"actions": [ - {"type": "spend", "asset_id": "%s", "amount": 100}, - {"type": "control_account", "asset_id": "%s", "amount": 100, "account_id": "%s"} + { + "type":"spend_account_unspent_output", + "receiver":null, + "output_id":"73d1e97c7bcf2b084f936a40f4f2a72e909417f2b46699e8659fa4c4feddb98d", + "reference_data":{} + }, + {"type": "issue", "asset_id": "%s", "amount": 100}, + {"type": "control_account", "asset_id": "%s", "amount": 100, "account_id": "%s"}, + {"type": "control_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 8888888888, "account_id": "%s"} ]}` - buildReqStr := fmt.Sprintf(buildReqFmt, asset[0].ID.String(), asset[0].ID.String(), account[0].ID) + buildReqStr := fmt.Sprintf(buildReqFmt, assets[0].ID.String(), assets[0].ID.String(), accounts[0].ID, accounts[0].ID) var buildReq bchain.BuildRequest err := stdjson.Unmarshal([]byte(buildReqStr), &buildReq) if err != nil { - fmt.Printf("json Unmarshal error.") + fmt.Println(err) } tpl := make([]txbuilder.Template, 1) client.Call(context.Background(), "/build-transaction", []*bchain.BuildRequest{&buildReq}, &tpl) fmt.Printf("tpl:%v\n", tpl) - /* // sign-transaction - err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprv_asset.XPub()}, func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte) ([]byte, error) { - derived := xprv_asset.Derive(path) - return derived.Sign(data[:]), nil - }) - if err != nil { - fmt.Printf("sign-transaction error. err:%v\n", err) + // sign-transaction1-Issue + err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprvGold.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { + derived := xprvGold.Derive(path) + return derived.Sign(data[:]), nil + }) + if err != nil { + fmt.Printf("sign-transaction error. err:%v\n", err) + os.Exit(0) + } + + fmt.Printf("sign tpl:%v\n", tpl[0]) + fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0]) + fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0]) + + // submit-transaction1-Issue + var submitResponse interface{} + submitArg := bchain.SubmitArg{Transactions: tpl, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"} + client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse) + fmt.Printf("submit transaction:%v\n", submitResponse) + + //Issue result: + //alice + + fmt.Println("===========================wait to buid accountutxos.db===============================================") + time.Sleep(time.Second * 2) + + // Build Transaction2-Spend_account + fmt.Printf("To build transaction2:\n") + buildReqFmt2 := ` + {"actions": [ + {"type": "spend_account", "asset_id": "%s", "amount": 40, "account_id": "%s"}, + {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 10000000, "account_id": "%s"}, + {"type": "control_account", "asset_id": "%s", "amount": 40, "account_id": "%s"}, + {"type": "control_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 5000000, "account_id": "%s"} + ]}` + buildReqStr2 := fmt.Sprintf(buildReqFmt2, assets[0].ID.String(), accounts[0].ID, accounts[0].ID, assets[0].ID.String(), accounts[1].ID, accounts[1].ID) + + var buildReq2 bchain.BuildRequest + err = stdjson.Unmarshal([]byte(buildReqStr2), &buildReq2) + if err != nil { + fmt.Println(err) + } + + tpl2 := make([]txbuilder.Template, 1) + client.Call(context.Background(), "/build-transaction", []*bchain.BuildRequest{&buildReq2}, &tpl2) + fmt.Printf("tpl2:%v\n", tpl2) + + // sign-transaction2-Spend_account + err = txbuilder.Sign(context.Background(), &tpl2[0], []chainkd.XPub{xprvAlice.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { + derived := xprvAlice.Derive(path) + return derived.Sign(data[:]), nil + }) + if err != nil { + fmt.Printf("sign-transaction2 error. err:%v\n", err) + os.Exit(0) + } + + fmt.Printf("sign tpl2:%v\n", tpl2[0]) + fmt.Printf("sign tpl2's SigningInstructions:%v\n", tpl2[0].SigningInstructions[0]) + fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl2[0].SigningInstructions[0].SignatureWitnesses[0]) + + // submit-transaction2-Spend_account + var submitResponse2 interface{} + submitArg2 := bchain.SubmitArg{Transactions: tpl2, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"} + client.Call(context.Background(), "/submit-transaction", submitArg2, &submitResponse2) + fmt.Printf("submit2 transaction:%v\n", submitResponse2) + + //Spend_account result: + //alice + //bob + //fee 10000000-5000000 + + fmt.Println("===========================wait to buid accountutxos.db===============================================") + time.Sleep(time.Second * 2) + + // Build Transaction3-Spend_account_utxo + fmt.Printf("To build transaction3:\n") + + // Get one UTXO + var tmp accUTXOShort + var in requestQuery + var amount uint64 + + responses := make([]interface{}, 0) + + client.Call(context.Background(), "/list-unspent-outputs", in, &responses) + if len(responses) > 0 { + for i, item := range responses { + itemString, _ := item.(string) + err = stdjson.Unmarshal(stdjson.RawMessage(itemString), &tmp) + if err != nil { + fmt.Printf("Spend_account_utxo: test fail, err:%v\n", err) + os.Exit(0) + } + if accounts[0].ID == tmp.AccountID && + assets[0].ID.String() == tmp.AssetID { + //get one alice gold utxo + fmt.Println(i, "-----", item) + break + } } - fmt.Printf("sign tpl:%v\n", tpl[0]) - fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0]) - fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0]) - - // submit-transaction - var submitResponse interface{} - submitArg := bc.SubmitArg{tpl, json.Duration{time.Duration(1000000)}, "none"} - client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse) - fmt.Printf("submit transaction:%v\n", submitResponse) - */ + } + + if tmp.AccountID == "" { + fmt.Printf("Spend_account_utxo: get on utxo fail\n") + os.Exit(0) + } + amount, _ = strconv.ParseUint(tmp.Amount, 10, 64) + fmt.Printf("Get one accUTXOShort: %v\n", tmp) + + buildReqFmt3 := ` + {"actions": [ + {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 10000000, "account_id": "%s"}, + {"type": "spend_account_unspent_output", "output_id": "%s"}, + {"type": "control_account", "asset_id": "%s", "amount": %d, "account_id": "%s"} + ]}` + buildReqStr3 := fmt.Sprintf(buildReqFmt3, accounts[0].ID, tmp.OutputID, tmp.AssetID, amount, accounts[1].ID) + + var buildReq3 bchain.BuildRequest + err = stdjson.Unmarshal([]byte(buildReqStr3), &buildReq3) + if err != nil { + fmt.Println(err) + } + + tpl3 := make([]txbuilder.Template, 1) + client.Call(context.Background(), "/build-transaction", []*bchain.BuildRequest{&buildReq3}, &tpl3) + fmt.Printf("tpl3:%v\n", tpl3) + + // sign-transaction3-Spend_account_utxo + err = txbuilder.Sign(context.Background(), &tpl3[0], []chainkd.XPub{xprvAlice.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { + derived := xprvAlice.Derive(path) + return derived.Sign(data[:]), nil + }) + if err != nil { + fmt.Printf("sign-transaction3 error. err:%v\n", err) + os.Exit(0) + } + + fmt.Printf("sign tpl3:%v\n", tpl2[0]) + fmt.Printf("sign tpl3's SigningInstructions:%v\n", tpl3[0].SigningInstructions[0]) + fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl3[0].SigningInstructions[0].SignatureWitnesses[0]) + + // submit-transaction3-Spend_account_utxo + var submitResponse3 interface{} + submitArg3 := bchain.SubmitArg{Transactions: tpl3, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"} + client.Call(context.Background(), "/submit-transaction", submitArg3, &submitResponse3) + fmt.Printf("submit3 transaction:%v\n", submitResponse3) + fmt.Println("==============test end===============") + //Spend_account_utxo result: + //alice + //bob + //fee 10000000 } diff --git a/protocol/block.go b/protocol/block.go index 49e34688..2a1f5b49 100644 --- a/protocol/block.go +++ b/protocol/block.go @@ -54,7 +54,8 @@ func (c *Chain) ValidateBlock(block, prev *legacy.Block) error { // ApplyValidBlock creates an updated snapshot without validating the // block. func (c *Chain) ApplyValidBlock(block *legacy.Block) (*state.Snapshot, error) { - newSnapshot := state.Copy(c.state.snapshot) + _, curSnapshot := c.State() + newSnapshot := state.Copy(curSnapshot) err := newSnapshot.ApplyBlock(legacy.MapBlock(block)) if err != nil { return nil, err diff --git a/sync/idempotency/group.go b/sync/idempotency/group.go new file mode 100644 index 00000000..b35c2045 --- /dev/null +++ b/sync/idempotency/group.go @@ -0,0 +1,75 @@ +/* +Copyright 2012 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package idempotency provides a duplicate function call suppression +// mechanism. It is a lightly modified version of groupcache's +// singleflight package that does not forget keys until explicitly +// told to. +package idempotency + +import "sync" + +// call is an in-flight or completed Once call +type call struct { + wg sync.WaitGroup + val interface{} + err error +} + +// Group represents a class of work and forms a namespace in which +// units of work can be executed with duplicate suppression. +type Group struct { + mu sync.Mutex // protects m + m map[string]*call // lazily initialized +} + +// Once executes and returns the results of the given function, making +// sure that only one execution for a given key happens until the +// key is explicitly forgotten. If a duplicate comes in, the duplicate +// caller waits for the original to complete and receives the same results. +func (g *Group) Once(key string, fn func() (interface{}, error)) (interface{}, error) { + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + g.mu.Unlock() + c.wg.Wait() + return c.val, c.err + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + c.val, c.err = fn() + if c.err != nil { + g.mu.Lock() + delete(g.m, key) + g.mu.Unlock() + } + c.wg.Done() + + return c.val, c.err +} + +// Forget forgets a key, allowing the next call for the key to execute +// the function. +func (g *Group) Forget(key string) { + g.mu.Lock() + delete(g.m, key) + g.mu.Unlock() +} diff --git a/sync/idempotency/group_test.go b/sync/idempotency/group_test.go new file mode 100644 index 00000000..06fd17db --- /dev/null +++ b/sync/idempotency/group_test.go @@ -0,0 +1,85 @@ +/* +Copyright 2012 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package idempotency + +import ( + "errors" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" +) + +func TestOnce(t *testing.T) { + var g Group + v, err := g.Once("key", func() (interface{}, error) { + return "bar", nil + }) + if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { + t.Errorf("Once = %v; want %v", got, want) + } + if err != nil { + t.Errorf("Once error = %v", err) + } +} + +func TestOnceErr(t *testing.T) { + var g Group + someErr := errors.New("Some error") + v, err := g.Once("key", func() (interface{}, error) { + return nil, someErr + }) + if err != someErr { + t.Errorf("Once error = %v; want someErr", err) + } + if v != nil { + t.Errorf("unexpected non-nil value %#v", v) + } +} + +func TestOnceDupSuppress(t *testing.T) { + var g Group + c := make(chan string) + var calls int32 + fn := func() (interface{}, error) { + atomic.AddInt32(&calls, 1) + return <-c, nil + } + + const n = 10 + var wg sync.WaitGroup + for i := 0; i < n; i++ { + wg.Add(1) + go func() { + v, err := g.Once("key", fn) + if err != nil { + t.Errorf("Once error: %v", err) + } + if v.(string) != "bar" { + t.Errorf("got %q; want %q", v, "bar") + } + wg.Done() + }() + } + time.Sleep(100 * time.Millisecond) // let goroutines above block + c <- "bar" + wg.Wait() + if got := atomic.LoadInt32(&calls); got != 1 { + t.Errorf("number of calls = %d; want 1", got) + } +} -- 2.11.0