diff --git a/Makefile b/Makefile index 551294ce..c9e020f1 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# default target is 'all' +all: + SHELL:=/bin/bash REPO := $(shell pwd) GOFILES_NOVENDOR := $(shell GOFLAGS="-mod=vendor" go list -f "{{.Dir}}" ./...) @@ -24,7 +27,7 @@ export GOFLAGS=-mod=vendor define build @go build -ldflags " \ -X github.com/fractalplatform/fractal/cmd/utils.commit=$(shell cat commit_hash.txt) \ - -X github.com/fractalplatform/fractal/cmd/utils.date=$(shell date '+%Y-%m-%d') \ + -X github.com/fractalplatform/fractal/cmd/utils.date=$(shell date '+%Y-%m-%d-%H:%M:%S') \ -X 'github.com/fractalplatform/fractal/cmd/utils.goversion=$(shell go version)'" \ -o ${REPO}/build/bin/$(1) ./cmd/$(1) endef diff --git a/accountmanager/accountmanager.go b/accountmanager/accountmanager.go index 4b724e15..447d192b 100644 --- a/accountmanager/accountmanager.go +++ b/accountmanager/accountmanager.go @@ -136,6 +136,7 @@ func SetAccountNameConfig(config *Config) bool { accountNameLength = config.AccountNameMaxLength return true } + func GetAccountNameRegExp() *regexp.Regexp { return acctRegExp } @@ -619,6 +620,7 @@ func (am *AccountManager) getParentAccount(accountName common.Name, parentIndex // RecoverTx Make sure the transaction is signed properly and validate account authorization. func (am *AccountManager) RecoverTx(signer types.Signer, tx *types.Transaction) error { + authorVersion := make(map[common.Name]common.Hash) for _, action := range tx.GetActions() { pubs, err := types.RecoverMultiKey(signer, action, tx) if err != nil { @@ -646,7 +648,6 @@ func (am *AccountManager) RecoverTx(signer types.Signer, tx *types.Transaction) } } - authorVersion := make(map[common.Name]common.Hash) for name, acctAuthor := range recoverRes.acctAuthors { var count uint64 for _, weight := range acctAuthor.indexWeight { @@ -664,6 +665,53 @@ func (am *AccountManager) RecoverTx(signer types.Signer, tx *types.Transaction) types.StoreAuthorCache(action, authorVersion) } + if tx.PayerExist() { + for _, action := range tx.GetActions() { + pubs, err := types.RecoverPayerMultiKey(signer, action, tx) + if err != nil { + return err + } + + if uint64(len(pubs)) > params.MaxSignLength { + return fmt.Errorf("exceed max sign length, want most %d, actual is %d", params.MaxSignLength, len(pubs)) + } + + sig := action.PayerSignature() + if sig == nil { + return fmt.Errorf("payer signature is nil") + } + parentIndex := sig.ParentIndex + signSender, err := am.getParentAccount(action.Payer(), parentIndex) + if err != nil { + return err + } + recoverRes := &recoverActionResult{make(map[common.Name]*accountAuthor)} + for i, pub := range pubs { + index := sig.SignData[uint64(i)].Index + if uint64(len(index)) > params.MaxSignDepth { + return fmt.Errorf("exceed max sign depth, want most %d, actual is %d", params.MaxSignDepth, len(index)) + } + + if err := am.ValidSign(signSender, pub, index, recoverRes); err != nil { + return err + } + } + + for name, acctAuthor := range recoverRes.acctAuthors { + var count uint64 + for _, weight := range acctAuthor.indexWeight { + count += weight + } + threshold := acctAuthor.threshold + if count < threshold { + return fmt.Errorf("account %s want threshold %d, but actual is %d", name, threshold, count) + } + authorVersion[name] = acctAuthor.version + } + + types.StoreAuthorCache(action, authorVersion) + } + } return nil } @@ -1337,12 +1385,12 @@ func (am *AccountManager) IssueAsset(fromName common.Name, asset IssueAsset, num } //IncAsset2Acct increase asset and add amount to accout balance -func (am *AccountManager) IncAsset2Acct(fromName common.Name, toName common.Name, assetID uint64, amount *big.Int) error { +func (am *AccountManager) IncAsset2Acct(fromName common.Name, toName common.Name, assetID uint64, amount *big.Int, forkID uint64) error { if err := am.ast.CheckOwner(fromName, assetID); err != nil { return err } - if err := am.ast.IncreaseAsset(fromName, assetID, amount); err != nil { + if err := am.ast.IncreaseAsset(fromName, assetID, amount, forkID); err != nil { return err } return nil @@ -1365,7 +1413,7 @@ func (am *AccountManager) process(accountManagerContext *types.AccountManagerCon var fromAccountExtra []common.Name fromAccountExtra = append(fromAccountExtra, accountManagerContext.FromAccountExtra...) - if err := action.Check(accountManagerContext.ChainConfig); err != nil { + if err := action.Check(curForkID, accountManagerContext.ChainConfig); err != nil { return nil, err } @@ -1458,7 +1506,7 @@ func (am *AccountManager) process(accountManagerContext *types.AccountManagerCon return nil, ErrNegativeAmount } - if err := am.IncAsset2Acct(action.Sender(), inc.To, inc.AssetID, inc.Amount); err != nil { + if err := am.IncAsset2Acct(action.Sender(), inc.To, inc.AssetID, inc.Amount, curForkID); err != nil { return nil, err } @@ -1508,7 +1556,7 @@ func (am *AccountManager) process(accountManagerContext *types.AccountManagerCon return nil, err } - if err := am.ast.UpdateAsset(action.Sender(), asset.AssetID, asset.Founder); err != nil { + if err := am.ast.UpdateAsset(action.Sender(), asset.AssetID, asset.Founder, curForkID); err != nil { return nil, err } case types.SetAssetOwner: diff --git a/accountmanager/accountmanager_test.go b/accountmanager/accountmanager_test.go index 1148d470..c21a931a 100644 --- a/accountmanager/accountmanager_test.go +++ b/accountmanager/accountmanager_test.go @@ -1455,7 +1455,7 @@ func TestAccountManager_IncAsset2Acct(t *testing.T) { sdb: tt.fields.sdb, ast: tt.fields.ast, } - if err := am.IncAsset2Acct(tt.args.fromName, tt.args.toName, tt.args.AssetID, tt.args.amount); (err != nil) != tt.wantErr { + if err := am.IncAsset2Acct(tt.args.fromName, tt.args.toName, tt.args.AssetID, tt.args.amount, 4); (err != nil) != tt.wantErr { t.Errorf("%q. AccountManager.IncAsset2Acct() error = %v, wantErr %v", tt.name, err, tt.wantErr) } } @@ -2226,3 +2226,89 @@ func Test_CheckAssetContract(t *testing.T) { } } } + +func TestAccountManager_CreateAccountFork1(t *testing.T) { + type fields struct { + sdb *state.StateDB + ast *asset.Asset + } + pubkey := new(common.PubKey) + pubkey2 := new(common.PubKey) + pubkey.SetBytes([]byte("abcde123456789")) + + pubkey3, _ := GeneragePubKey() + type args struct { + fromName common.Name + accountName common.Name + founderName common.Name + pubkey common.PubKey + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + {"createAccount", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("a111222332afork"), common.Name(""), pubkey3}, false}, + {"createAccountSub", fields{sdb, ast}, args{common.Name("a111222332afork"), common.Name("a111222332afork.sub1"), common.Name(""), pubkey3}, false}, + {"createAccountWithEmptyKey", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("a123456789fork"), common.Name(""), *pubkey2}, false}, + {"createAccountWithEmptyKey", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("a123456789afork"), common.Name(""), *pubkey}, false}, + {"createAccountWithInvalidName", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("a12345678-fork"), common.Name(""), *pubkey}, true}, + {"createAccountWithInvalidName", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("a123456789aeeefork"), common.Name(""), *pubkey}, true}, + {"createAccountNameInvalid", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("a12345678b"), common.Name(""), pubkey3}, true}, + {"createinvalidAccount0", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("\ttesttestf1"), common.Name(""), *pubkey}, true}, + {"createinvalidAccount1", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("testtestf1.."), common.Name(""), *pubkey}, true}, + {"createinvalidAccount2", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("fractal.account"), common.Name(""), *pubkey}, true}, + {"createinvalidAccount3", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("fractal.founder.1"), common.Name(""), *pubkey}, true}, + {"createinvalidAccount4", fields{sdb, ast}, args{common.Name("fractal.founder"), common.Name("fractal.founder.12"), common.Name(""), *pubkey}, true}, + {"createinvalidAccount5", fields{sdb, ast}, args{common.Name("a111222332afork"), common.Name("a111222332afork.founder1234"), common.Name(""), *pubkey}, true}, + } + for _, tt := range tests { + am := &AccountManager{ + sdb: tt.fields.sdb, + ast: tt.fields.ast, + } + if err := am.CreateAccount(tt.args.fromName, tt.args.accountName, tt.args.founderName, 0, 1, tt.args.pubkey, ""); (err != nil) != tt.wantErr { + t.Errorf("%q. AccountManager.CreateAccount() error = %v, wantErr %v", tt.name, err, tt.wantErr) + } + } +} + +func TestAccountManager_AccountHaveCode(t *testing.T) { + type fields struct { + sdb *state.StateDB + ast *asset.Asset + } + + field := &fields{sdb, ast} + + pubkey, _ := GeneragePubKey() + + am := &AccountManager{ + sdb: field.sdb, + ast: field.ast, + } + + err := am.CreateAccount(common.Name("fractal.founder"), common.Name("a111222332code"), common.Name(""), 0, 1, pubkey, "") + if err != nil { + t.Errorf("TestAccountManager_AccountHaveCode. AccountManager.CreateAccount() error = %v", err) + } + + haveCode, err := am.AccountHaveCode(common.Name("a111222332code")) + + if err != nil || haveCode == true { + t.Errorf("TestAccountManager_AccountHaveCode. account have code error = %v", err) + } + + _, err = am.SetCode(common.Name("a111222332code"), []byte("abcde123456789")) + + if err != nil { + t.Errorf("TestAccountManager_AccountHaveCode. set code error = %v", err) + } + + haveCode, err = am.AccountHaveCode(common.Name("a111222332code")) + + if err != nil || haveCode == false { + t.Errorf("TestAccountManager_AccountHaveCode. account not have code error = %v", err) + } +} diff --git a/asset/asset.go b/asset/asset.go index 8aee99fa..35198aac 100644 --- a/asset/asset.go +++ b/asset/asset.go @@ -23,6 +23,7 @@ import ( "strconv" "strings" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/params" @@ -294,6 +295,11 @@ func (a *Asset) IssueAssetObject(ao *AssetObject) (uint64, error) { //IssueAsset issue asset func (a *Asset) IssueAsset(assetName string, number uint64, forkID uint64, symbol string, amount *big.Int, dec uint64, founder common.Name, owner common.Name, limit *big.Int, contract common.Name, description string) (uint64, error) { + if forkID >= params.ForkID4 { + if amount.Cmp(math.MaxBig256) > 0 { + return 0, ErrAmountOverMax256 + } + } _, err := a.GetAssetIDByName(assetName) if err != nil && err != ErrAssetNotExist { return 0, err @@ -355,7 +361,7 @@ func (a *Asset) DestroyAsset(accountName common.Name, assetID uint64, amount *bi } //IncreaseAsset increase asset, upperlimit == 0 means no upper limit -func (a *Asset) IncreaseAsset(accountName common.Name, assetID uint64, amount *big.Int) error { +func (a *Asset) IncreaseAsset(accountName common.Name, assetID uint64, amount *big.Int, forkID uint64) error { if accountName == "" { return ErrAccountNameNull } @@ -372,6 +378,11 @@ func (a *Asset) IncreaseAsset(accountName common.Name, assetID uint64, amount *b if asset == nil { return ErrAssetNotExist } + if forkID >= params.ForkID4 { + if (new(big.Int).Add(asset.GetAssetAmount(), amount)).Cmp(math.MaxBig256) > 0 { + return ErrAmountOverMax256 + } + } // if asset.GetAssetOwner() != accountName { // return ErrOwnerMismatch // } @@ -398,7 +409,7 @@ func (a *Asset) IncreaseAsset(accountName common.Name, assetID uint64, amount *b } //UpdateAsset change asset info -func (a *Asset) UpdateAsset(accountName common.Name, assetID uint64, founderName common.Name) error { +func (a *Asset) UpdateAsset(accountName common.Name, assetID uint64, founderName common.Name, curForkID uint64) error { if accountName == "" { return ErrAccountNameNull } @@ -412,8 +423,16 @@ func (a *Asset) UpdateAsset(accountName common.Name, assetID uint64, founderName // if asset.GetAssetOwner() != accountName { // return ErrOwnerMismatch // } - - asset.SetAssetFounder(founderName) + if curForkID >= params.ForkID4 { + if len(founderName.String()) == 0 { + assetOwner := asset.GetAssetOwner() + asset.SetAssetFounder(assetOwner) + } else { + asset.SetAssetFounder(founderName) + } + } else { + asset.SetAssetFounder(founderName) + } return a.SetAssetObject(asset) } diff --git a/asset/asset_object_test.go b/asset/asset_object_test.go index 284dbcda..841845b8 100644 --- a/asset/asset_object_test.go +++ b/asset/asset_object_test.go @@ -26,13 +26,14 @@ import ( func Test_newAssetObject(t *testing.T) { type args struct { - assetName string - symbol string - amount *big.Int - dec uint64 - founder common.Name - owner common.Name - UpperLimit *big.Int + assetName string + symbol string + amount *big.Int + dec uint64 + founder common.Name + owner common.Name + UpperLimit *big.Int + description string } tests := []struct { name string @@ -41,19 +42,27 @@ func Test_newAssetObject(t *testing.T) { wantErr bool }{ // TODO: Add test cases. - {"normal", args{"ft", "ft", big.NewInt(2), 18, common.Name(""), common.Name("a123"), big.NewInt(999999)}, &AssetObject{0, 0, 0, "ft", "ft", big.NewInt(2), 18, common.Name(""), common.Name("a123"), big.NewInt(2), big.NewInt(999999), common.Name(""), ""}, false}, - {"shortname", args{"z", "z", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999)}, nil, true}, - {"longname", args{"ftt0123456789ftt12", "zz", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999)}, nil, true}, - {"emptyname", args{"", "z", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999)}, nil, true}, - {"symbolempty", args{"ft", "", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999)}, nil, true}, - {"amount==0", args{"ft", "z", big.NewInt(-1), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999)}, nil, true}, - {"ownerempty", args{"ft", "z", big.NewInt(2), 18, common.Name(""), common.Name(""), big.NewInt(999999)}, nil, true}, - {"shortsymbol", args{"ft", "z", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999)}, nil, true}, - {"longsymbol", args{"ft", "ftt0123456789ftt1", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999)}, nil, true}, - {"emptyname", args{"ft", "#ip0123456789ft", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999)}, nil, true}, + {"normal", args{"ft", "ft", big.NewInt(2), 18, common.Name(""), common.Name("a123"), big.NewInt(999999), ""}, &AssetObject{0, 0, 0, "ft", "ft", big.NewInt(2), 18, common.Name(""), common.Name("a123"), big.NewInt(2), big.NewInt(999999), common.Name(""), ""}, false}, + {"shortname", args{"z", "z", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999), ""}, nil, true}, + {"longname", args{"ftt0123456789ftt12", "zz", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999), ""}, nil, true}, + {"emptyname", args{"", "z", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999), ""}, nil, true}, + {"symbolempty", args{"ft", "", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999), ""}, nil, true}, + {"amount==0", args{"ft", "z", big.NewInt(-1), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999), ""}, nil, true}, + {"ownerempty", args{"ft", "z", big.NewInt(2), 18, common.Name(""), common.Name(""), big.NewInt(999999), ""}, nil, true}, + {"shortsymbol", args{"ft", "z", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999), ""}, nil, true}, + {"longsymbol", args{"ft", "ftt0123456789ftt1", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999), ""}, nil, true}, + {"emptyname", args{"ft", "#ip0123456789ft", big.NewInt(2), 18, common.Name("a123"), common.Name("a123"), big.NewInt(999999), ""}, nil, true}, + {"limiterror", args{"ft", "ft", big.NewInt(101), 18, common.Name(""), common.Name("a123"), big.NewInt(100), ""}, nil, true}, + {"descerror", args{"ft", "ft", big.NewInt(100), 18, common.Name(""), common.Name("a123"), big.NewInt(101), + "aaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbbaaaaaaaaaa" + + "bbbbbbbbbbaaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbb" + + "aaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbbaaaaaaaaaa" + + "bbbbbbbbbbaaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbb" + + "aaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbbaaaaaaaaaa" + + "bbbbbbbbbbaaaaaaaaaabbbbbbbbbbaaaaaaaaaabbbbbbbbbb"}, nil, true}, } for _, tt := range tests { - got, err := NewAssetObject(tt.args.assetName, 0, tt.args.symbol, tt.args.amount, tt.args.dec, tt.args.founder, tt.args.owner, tt.args.UpperLimit, common.Name(""), "") + got, err := NewAssetObject(tt.args.assetName, 0, tt.args.symbol, tt.args.amount, tt.args.dec, tt.args.founder, tt.args.owner, tt.args.UpperLimit, common.Name(""), tt.args.description) if (err != nil) != tt.wantErr { t.Errorf("%q. newAssetObject() error = %v, wantErr %v", tt.name, err, tt.wantErr) continue @@ -456,3 +465,90 @@ func TestAssetObject_SetAssetOwner(t *testing.T) { ao.SetAssetOwner(tt.args.owner) } } + +func TestAssetObject_AssetNumber(t *testing.T) { + ao := NewAssetObjectNoCheck("", uint64(0), "", big.NewInt(0), 0, common.Name(""), common.Name(""), big.NewInt(1000), common.Name(""), "") + // ao := &AssetObject{ + // UpperLimit: big.NewInt(1000), + // } + + id := uint64(1) + ao.SetAssetID(id) + if got := ao.GetAssetID(); got != id { + t.Errorf("AssetObject.GetAssetID() = %v, want %v", got, id) + } + + number := uint64(2) + ao.SetAssetNumber(number) + if got := ao.GetAssetNumber(); got != number { + t.Errorf("AssetObject.GetAssetNumber() = %v, want %v", got, number) + } + + stats := uint64(3) + ao.SetAssetStats(stats) + if got := ao.GetAssetStats(); got != stats { + t.Errorf("AssetObject.GetAssetStats() = %v, want %v", got, stats) + } + + symbol := "symbol" + ao.SetSymbol(symbol) + if got := ao.GetSymbol(); got != symbol { + t.Errorf("AssetObject.GetSymbol() = %v, want %v", got, symbol) + } + + decimals := uint64(4) + ao.SetDecimals(decimals) + if got := ao.GetDecimals(); got != decimals { + t.Errorf("AssetObject.GetDecimals() = %v, want %v", got, decimals) + } + + name := "name" + ao.SetAssetName(name) + if got := ao.GetAssetName(); got != name { + t.Errorf("AssetObject.GetAssetName() = %v, want %v", got, name) + } + + amount := big.NewInt(5) + ao.SetAssetAmount(amount) + if got := ao.GetAssetAmount(); got != amount { + t.Errorf("AssetObject.GetAssetAmount() = %v, want %v", got, amount) + } + + issue := big.NewInt(6) + ao.SetAssetAddIssue(issue) + if got := ao.GetAssetAddIssue(); got != issue { + t.Errorf("AssetObject.GetAssetAddIssue() = %v, want %v", got, issue) + } + + if got := ao.GetUpperLimit(); got != ao.UpperLimit { + t.Errorf("AssetObject.GetUpperLimit() = %v, want %v", got, ao.UpperLimit) + } + + contract := common.Name("contract") + ao.SetAssetContract(contract) + if got := ao.GetAssetContract(); got != contract { + t.Errorf("AssetObject.GetAssetContract() = %v, want %v", got, contract) + } + if got := ao.GetContract(); got != contract { + t.Errorf("AssetObject.GetContract() = %v, want %v", got, contract) + } + + founder := common.Name("founder") + ao.SetAssetFounder(founder) + if got := ao.GetAssetFounder(); got != founder { + t.Errorf("AssetObject.GetAssetFounder() = %v, want %v", got, founder) + } + + owner := common.Name("owner") + ao.SetAssetOwner(owner) + if got := ao.GetAssetOwner(); got != owner { + t.Errorf("AssetObject.GetAssetFounder() = %v, want %v", got, owner) + } + + desc := "desc" + ao.SetAssetDescription(desc) + if got := ao.GetAssetDescription(); got != desc { + t.Errorf("AssetObject.GetAssetFounder() = %v, want %v", got, desc) + } + +} diff --git a/asset/asset_test.go b/asset/asset_test.go index e0e96d49..30b8b104 100644 --- a/asset/asset_test.go +++ b/asset/asset_test.go @@ -455,7 +455,7 @@ func TestAsset_IncreaseAsset(t *testing.T) { a := &Asset{ sdb: tt.fields.sdb, } - if err := a.IncreaseAsset(tt.args.accountName, tt.args.AssetID, tt.args.amount); (err != nil) != tt.wantErr { + if err := a.IncreaseAsset(tt.args.accountName, tt.args.AssetID, tt.args.amount, 4); (err != nil) != tt.wantErr { t.Errorf("%q. Asset.IncreaseAsset() error = %v, wantErr %v", tt.name, err, tt.wantErr) } } @@ -501,6 +501,7 @@ func TestAsset_UpdateAsset(t *testing.T) { AssetID uint64 Owner common.Name founder common.Name + forkID uint64 } tests := []struct { name string @@ -509,17 +510,19 @@ func TestAsset_UpdateAsset(t *testing.T) { wantErr bool }{ // TODO: Add test cases - {"nilname", fields{assetDB}, args{common.Name(""), 1, common.Name(""), common.Name("")}, true}, - {"wrongAssetID", fields{assetDB}, args{common.Name("11"), 0, common.Name(""), common.Name("")}, false}, - {"wrongamount", fields{assetDB}, args{common.Name("11"), 123, common.Name(""), common.Name("")}, true}, - {"nilfounder", fields{assetDB}, args{common.Name("a123456789afff"), 1, common.Name("a123456789aeee"), common.Name("")}, false}, - {"normal", fields{assetDB}, args{common.Name("a123456789afff"), 1, common.Name("a123456789afff"), common.Name("a123456789afff")}, false}, + {"nilname", fields{assetDB}, args{common.Name(""), 1, common.Name(""), common.Name(""), 0}, true}, + {"wrongAssetID", fields{assetDB}, args{common.Name("11"), 0, common.Name(""), common.Name(""), 0}, false}, + {"wrongamount", fields{assetDB}, args{common.Name("11"), 123, common.Name(""), common.Name(""), 0}, true}, + {"nilfounder", fields{assetDB}, args{common.Name("a123456789afff"), 1, common.Name("a123456789aeee"), common.Name(""), 0}, false}, + {"nilfounder", fields{assetDB}, args{common.Name("a123456789afff"), 1, common.Name("a123456789aeee"), common.Name(""), 4}, false}, + {"nilfounder", fields{assetDB}, args{common.Name("a123456789afff"), 1, common.Name("a123456789aeee"), common.Name("a123456789afff"), 4}, false}, + {"normal", fields{assetDB}, args{common.Name("a123456789afff"), 1, common.Name("a123456789afff"), common.Name("a123456789afff"), 0}, false}, } for _, tt := range tests { a := &Asset{ sdb: tt.fields.sdb, } - if err := a.UpdateAsset(tt.args.accountName, tt.args.AssetID, tt.args.founder); (err != nil) != tt.wantErr { + if err := a.UpdateAsset(tt.args.accountName, tt.args.AssetID, tt.args.founder, tt.args.forkID); (err != nil) != tt.wantErr { t.Errorf("%q. Asset.updateAsset() error = %v, wantErr %v", tt.name, err, tt.wantErr) } } diff --git a/asset/error.go b/asset/error.go index 9e206ff5..8c194b8d 100644 --- a/asset/error.go +++ b/asset/error.go @@ -34,4 +34,5 @@ var ( ErrAssetManagerNotExist = errors.New("asset manager name not exist") ErrDetailTooLong = errors.New("detail info exceed maximum") ErrNegativeAmount = errors.New("negative amount") + ErrAmountOverMax256 = errors.New("amount over max uint256") ) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 703b2963..75d068e9 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -280,11 +280,6 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { return batch.Write() } -// GasLimit returns the gas limit of the current HEAD block. -func (bc *BlockChain) GasLimit() uint64 { - return bc.CurrentBlock().GasLimit() -} - // CurrentBlock retrieves the current head block of the canonical chain. func (bc *BlockChain) CurrentBlock() *types.Block { return bc.currentBlock.Load().(*types.Block) diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index b720ab2c..f8e9715a 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -20,6 +20,8 @@ import ( "testing" "github.com/ethereum/go-ethereum/log" + "github.com/fractalplatform/fractal/processor/vm" + "github.com/fractalplatform/fractal/txpool" ) // So we can deterministically seed different blockchains @@ -66,3 +68,43 @@ func TestSystemForkChain(t *testing.T) { // check if is complete block chain checkCompleteChain(t, chain) } + +func TestBadBlockHashes(t *testing.T) { + genesis := DefaultGenesis() + chain := newCanonical(t, genesis) + defer chain.Stop() + + _, blocks := makeNewChain(t, genesis, chain, 10, canonicalSeed) + + chain.badHashes[blocks[2].Header().Hash()] = true + + _, err := chain.InsertChain(blocks) + if err != ErrBlacklistedHash { + t.Errorf("error mismatch: have: %v, want: %v", err, ErrBlacklistedHash) + } + + // test NewBlockChain()badblock err + delete(chain.badHashes, blocks[2].Header().Hash()) + + _, err = chain.InsertChain(blocks) + if err != nil { + t.Fatal(err) + } + + newChain, err := NewBlockChain(chain.db, false, vm.Config{}, chain.chainConfig, + []string{blocks[2].Header().Hash().String()}, 0, txpool.SenderCacher) + if err != nil { + t.Fatal(err) + } + defer newChain.Stop() + + // if db have bad block then block will be reset, newChain.CurrentBlock().Hash() must equal chain or newchain genesis block hash. + if newChain.CurrentBlock().Hash() != chain.GetBlockByNumber(0).Hash() || + newChain.CurrentBlock().Hash() != newChain.GetBlockByNumber(0).Hash() { + t.Fatalf("cur hash %x , genesis hash %v", newChain.CurrentBlock().Hash(), + newChain.Genesis().Hash()) + } + + t.Log(newChain.CurrentBlock().Hash().String()) + t.Log(newChain.Genesis().Hash().String()) +} diff --git a/blockchain/downloader.go b/blockchain/downloader.go index 43089953..c2719c6c 100644 --- a/blockchain/downloader.go +++ b/blockchain/downloader.go @@ -417,7 +417,8 @@ func (dl *Downloader) shortcutDownload(status *stationStatus, startNumber uint64 } // return true means need call again -func (dl *Downloader) multiplexDownload(status *stationStatus) bool { +func (dl *Downloader) multiplexDownload() bool { + status := dl.bestStation() log.Debug("multiplexDownload start") defer log.Debug("multiplexDownload end") if status == nil { @@ -467,10 +468,15 @@ func (dl *Downloader) multiplexDownload(status *stationStatus) bool { ancestor, ancestorHash, err := dl.findAncestor(stationSearch, status.station, headNumber, status.ancestor, status.errCh) if err != nil { log.Warn("ancestor err", "err", err, "errID:", err.eid) + router.AddErr(status.station, 1) if err.eid == notFind { log.Warn("Disconnect because ancestor not find:", "node:", adaptor.GetFnode(status.station)) router.SendTo(nil, nil, router.OneMinuteLimited, status.station) // disconnect and put into blacklist + } else if router.Err(status.station) > 50 { + log.Warn("Disconnect because too much error:", "node:", adaptor.GetFnode(status.station)) + router.SendTo(nil, nil, router.OneMinuteLimited, status.station) // disconnect and put into blacklist } + return false } log.Debug("downloader ancestor:", "ancestor", ancestor) @@ -556,19 +562,16 @@ func (dl *Downloader) loopStart() { func (dl *Downloader) loop() { defer dl.loopWG.Done() - download := func() { - //for status := dl.bestStation(); dl.download(status); { - for status := dl.bestStation(); dl.multiplexDownload(status); { - } - } timer := time.NewTimer(10 * time.Second) for { select { case <-dl.quit: return case <-dl.downloadTrigger: - download() timer.Stop() + if dl.multiplexDownload() { + dl.loopStart() + } timer.Reset(10 * time.Second) case <-timer.C: dl.loopStart() @@ -716,18 +719,35 @@ func (task *downloadTask) Do() { }, downloadAmount, 0, false, }, task.worker.errCh) if err != nil || len(headers) != int(downloadAmount) { - log.Debug(fmt.Sprint("err-2:", err, len(headers), downloadAmount)) + log.Debug("download header failed", + "err", err, + "recvAmount", len(headers), + "taskAmount", downloadAmount, + ) return } if headers[0].Number.Uint64() != task.startNumber+1 || headers[0].ParentHash != task.startHash || headers[len(headers)-1].Number.Uint64() != task.endNumber || headers[len(headers)-1].Hash() != task.endHash { - log.Debug(fmt.Sprintf("e2-1 0d:%d\n0ed:%d\nsd:%d\nsed:%d", headers[0].Number.Uint64(), headers[len(headers)-1].Number.Uint64(), task.startNumber, task.endNumber)) - log.Debug(fmt.Sprintf("e2-2 0:%x\n0e:%x\ns:%x\nse:%x", headers[0].Hash(), headers[len(headers)-1].Hash(), task.startHash, task.endHash)) + log.Debug("download header don't match task", + "recv.Start.Number", headers[0].Number.Uint64(), + "recv.End.Number", headers[len(headers)-1].Number.Uint64(), + "recv.Start.ParentHash", headers[0].ParentHash, + "recv.End.Hash", headers[len(headers)-1].Hash(), + "task.Start.Number", task.startNumber, + "task.End.Number", task.endNumber, + "task.Start.Hash", task.startHash, + "task.End.Hash", task.endHash, + ) return } for i := 1; i < len(headers); i++ { if headers[i].ParentHash != headers[i-1].Hash() || headers[i].Number.Uint64() != headers[i-1].Number.Uint64()+1 { - log.Debug(fmt.Sprintf("err-3: phash:%x n->phash:%x\npn+1:%d n:%d", headers[i-1].Hash(), headers[i].ParentHash, headers[i-1].Number.Uint64()+1, headers[i].Number.Uint64())) + log.Debug("download headers are discontinuous", + "parent.number", headers[i-1].Number.Uint64(), + "parent.hash", headers[i-1].Hash(), + "n.number", headers[i].Number.Uint64(), + "n.parentHash", headers[i].ParentHash, + ) return } } @@ -741,7 +761,10 @@ func (task *downloadTask) Do() { bodies, err = getBlocks(station, remote, reqHashes, task.worker.errCh) if err != nil || len(bodies) != len(reqHashes) { - log.Debug(fmt.Sprint("err-4:", err, len(bodies), len(reqHashes))) + log.Debug("download blocks failed", + "err", err, + "recvAmount", len(bodies), + "taskAmount", len(reqHashes)) return } diff --git a/blockchain/genesis.go b/blockchain/genesis.go index 60bab804..45754d26 100644 --- a/blockchain/genesis.go +++ b/blockchain/genesis.go @@ -50,7 +50,7 @@ type GenesisAccount struct { // GenesisCandidate is an candidate in the state of the genesis block. type GenesisCandidate struct { Name string `json:"name,omitempty"` - URL string `json:"url,omitempty"` + Info string `json:"info,omitempty"` Stake *big.Int `json:"stake,omitempty"` } @@ -202,6 +202,7 @@ func (g *Genesis) ToBlock(db fdb.Database) (*types.Block, []*types.Receipt, erro SubAssetNameMinLength: 1, SubAssetNameMaxLength: 8, }) + am.SetAcctMangerName(common.StrToName(g.Config.AccountName)) at.SetAssetMangerName(common.StrToName(g.Config.AssetName)) fm.SetFeeManagerName(common.StrToName(g.Config.FeeName)) @@ -402,7 +403,7 @@ func (g *Genesis) ToBlock(db fdb.Database) (*types.Block, []*types.Receipt, erro if err := sys.SetCandidate(&dpos.CandidateInfo{ Epoch: epoch, Name: candidate.Name, - URL: candidate.URL, + Info: candidate.Info, Quantity: big.NewInt(0), TotalQuantity: big.NewInt(0), Number: number.Uint64(), diff --git a/cmd/ft/p2p.go b/cmd/ft/p2p.go index ab773ba5..1cb09ac3 100644 --- a/cmd/ft/p2p.go +++ b/cmd/ft/p2p.go @@ -28,141 +28,117 @@ var p2pCmd = &cobra.Command{ Args: cobra.NoArgs, } -var addCmd = &cobra.Command{ - Use: "add ", - Short: "Connecting to a remote node.", - Long: `Connecting to a remote node.`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var result bool - clientCall(ipcEndpoint, &result, "p2p_addPeer", args[0]) +var commonCall = func(method string) func(*cobra.Command, []string) { + return func(cmd *cobra.Command, args []string) { + params := make([]interface{}, len(args)) + for i, arg := range args { + params[i] = arg + } + result := clientCallRaw(ipcEndpoint, method, params...) printJSON(result) - }, + } } -var removeCmd = &cobra.Command{ - Use: "remove ", - Short: "Disconnects from a remote node if the connection exists.", - Long: `Disconnects from a remote node if the connection exists.`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var result bool - clientCall(ipcEndpoint, &result, "p2p_removePeer", args[0]) - printJSON(result) +var p2pSubCmds = []*cobra.Command{ + &cobra.Command{ + Use: "add ", + Short: "Connecting to a remote node.", + Long: `Connecting to a remote node.`, + Args: cobra.ExactArgs(1), + Run: commonCall("p2p_addPeer"), }, -} -var addtrustedCmd = &cobra.Command{ - Use: "addtrusted ", - Short: "Allows a remote node to always connect, even if slots are full.", - Long: `Allows a remote node to always connect, even if slots are full.`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var result bool - clientCall(ipcEndpoint, &result, "p2p_addTrustedPeer", args[0]) - printJSON(result) + &cobra.Command{ + Use: "remove ", + Short: "Disconnects from a remote node if the connection exists.", + Long: `Disconnects from a remote node if the connection exists.`, + Args: cobra.ExactArgs(1), + Run: commonCall("p2p_removePeer"), }, -} -var removetrustedCmd = &cobra.Command{ - Use: "removetrusted ", - Short: "Removes a remote node from the trusted peer set, but it does not disconnect it automatically.", - Long: `Removes a remote node from the trusted peer set, but it does not disconnect it automatically.`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var result bool - clientCall(ipcEndpoint, &result, "p2p_removeTrustedPeer", args[0]) - printJSON(result) + &cobra.Command{ + Use: "addtrusted ", + Short: "Allows a remote node to always connect, even if slots are full.", + Long: `Allows a remote node to always connect, even if slots are full.`, + Args: cobra.ExactArgs(1), + Run: commonCall("p2p_addTrustedPeer"), }, -} -var addbadCmd = &cobra.Command{ - Use: "addbad ", - Short: "Add a bad node in black list.", - Long: `Add a bad node in black list..`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var result bool - clientCall(ipcEndpoint, &result, "p2p_addBadNode", args[0]) - printJSON(result) + &cobra.Command{ + Use: "removetrusted ", + Short: "Removes a remote node from the trusted peer set, but it does not disconnect it automatically.", + Long: `Removes a remote node from the trusted peer set, but it does not disconnect it automatically.`, + Args: cobra.ExactArgs(1), + Run: commonCall("p2p_removeTrustedPeer"), }, -} -var removebadCmd = &cobra.Command{ - Use: "removebad ", - Short: "Removes a bad node from the black peer set.", - Long: `Removes a bad node from the black peer set.`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var result bool - clientCall(ipcEndpoint, &result, "p2p_removeBadNode", args[0]) - printJSON(result) + &cobra.Command{ + Use: "addbad ", + Short: "Add a bad node in black list.", + Long: `Add a bad node in black list..`, + Args: cobra.ExactArgs(1), + Run: commonCall("p2p_addBadNode"), }, -} -var countCmd = &cobra.Command{ - Use: "count", - Short: "Return number of connected peers.", - Long: `Return number of connected peers.`, - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - var result int - clientCall(ipcEndpoint, &result, "p2p_peerCount") - printJSON(result) + &cobra.Command{ + Use: "removebad ", + Short: "Removes a bad node from the black peer set.", + Long: `Removes a bad node from the black peer set.`, + Args: cobra.ExactArgs(1), + Run: commonCall("p2p_removeBadNode"), }, -} -var listCmd = &cobra.Command{ - Use: "list", - Short: "Return connected peers list.", - Long: `Return connected peers list.`, - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - var result []string - clientCall(ipcEndpoint, &result, "p2p_peers") - printJSONList(result) + &cobra.Command{ + Use: "count", + Short: "Return number of connected peers.", + Long: `Return number of connected peers.`, + Args: cobra.NoArgs, + Run: commonCall("p2p_peerCount"), }, -} -var badcountCmd = &cobra.Command{ - Use: "badcount", - Short: "Return number of bad nodes .", - Long: `Return number of bad nodes .`, - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - var result int - clientCall(ipcEndpoint, &result, "p2p_badNodesCount") - printJSON(result) + &cobra.Command{ + Use: "list", + Short: "Return connected peers list.", + Long: `Return connected peers list.`, + Args: cobra.NoArgs, + Run: commonCall("p2p_peers"), }, -} -var badlistCmd = &cobra.Command{ - Use: "badlist", - Short: "Return bad nodes list.", - Long: `Return bad nodes list.`, - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - var result []string - clientCall(ipcEndpoint, &result, "p2p_badNodes") - printJSONList(result) + &cobra.Command{ + Use: "badcount", + Short: "Return number of bad nodes .", + Long: `Return number of bad nodes .`, + Args: cobra.NoArgs, + Run: commonCall("p2p_badNodesCount"), }, -} -var selfnodeCmd = &cobra.Command{ - Use: "selfnode", - Short: "Return self enode url.", - Long: `Return self enode url.`, - Args: cobra.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - var result string - clientCall(ipcEndpoint, &result, "p2p_selfNode") - printJSON(result) + &cobra.Command{ + Use: "badlist", + Short: "Return bad nodes list.", + Long: `Return bad nodes list.`, + Args: cobra.NoArgs, + Run: commonCall("p2p_badNodes"), + }, + + &cobra.Command{ + Use: "selfnode", + Short: "Return self enode url.", + Long: `Return self enode url.`, + Args: cobra.NoArgs, + Run: commonCall("p2p_selfNode"), + }, + + &cobra.Command{ + Use: "seednodes", + Short: "Return seed enode url.", + Long: `Return seed enode url.`, + Args: cobra.NoArgs, + Run: commonCall("p2p_seedNodes"), }, } func init() { RootCmd.AddCommand(p2pCmd) - p2pCmd.AddCommand(addCmd, removeCmd, addtrustedCmd, removetrustedCmd, - addbadCmd, removebadCmd, countCmd, listCmd, badcountCmd, badlistCmd, selfnodeCmd) + p2pCmd.AddCommand(p2pSubCmds...) p2pCmd.PersistentFlags().StringVarP(&ipcEndpoint, "ipcpath", "i", defaultIPCEndpoint(params.ClientIdentifier), "IPC Endpoint path") } diff --git a/cmd/ft/root.go b/cmd/ft/root.go index a5ec539d..cf9e4d30 100644 --- a/cmd/ft/root.go +++ b/cmd/ft/root.go @@ -94,6 +94,7 @@ var RootCmd = &cobra.Command{ } func makeNode() (*node.Node, error) { + genesis := blockchain.DefaultGenesis() // set miner config SetupMetrics() // Make sure we have a valid genesis JSON @@ -105,13 +106,18 @@ func makeNode() (*node.Node, error) { } defer file.Close() - genesis := blockchain.DefaultGenesis() if err := json.NewDecoder(file).Decode(genesis); err != nil { return nil, fmt.Errorf("invalid genesis file: %v(%v)", ftCfgInstance.GenesisFile, err) } ftCfgInstance.FtServiceCfg.Genesis = genesis - } + } + block, _, err := genesis.ToBlock(nil) + if err != nil { + return nil, err + } + // p2p used to generate MagicNetID + ftCfgInstance.NodeCfg.P2PConfig.GenesisHash = block.Hash() return node.New(ftCfgInstance.NodeCfg) } diff --git a/cmd/ft/txpool.go b/cmd/ft/txpool.go index 2d16e697..66bb1c8d 100644 --- a/cmd/ft/txpool.go +++ b/cmd/ft/txpool.go @@ -32,10 +32,17 @@ var contentCmd = &cobra.Command{ Use: "content ", Short: "Returns the transactions contained within the transaction pool.", Long: `Returns the transactions contained within the transaction pool.`, - Args: cobra.ExactArgs(1), + Args: cobra.RangeArgs(0, 1), Run: func(cmd *cobra.Command, args []string) { - var result interface{} - clientCall(ipcEndpoint, &result, "txpool_content", parseBool(args[0])) + var ( + result interface{} + fullTx bool + ) + if len(args) == 1 { + fullTx = parseBool(args[0]) + } + + clientCall(ipcEndpoint, &result, "txpool_content", fullTx) printJSON(result) }, } @@ -64,16 +71,37 @@ var setGasPriceCmd = &cobra.Command{ }, } -var getTxCmd = &cobra.Command{ - Use: "gettx ", +var getTxsCmd = &cobra.Command{ + Use: "gettxs ", Short: "Returns the transaction for the given hash", Long: `Returns the transaction for the given hash`, + Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { var result []*types.RPCTransaction - clientCall(ipcEndpoint, &result, "txpool_getPoolTransactions", args) + clientCall(ipcEndpoint, &result, "txpool_getTransactions", args) printJSONList(result) }, } + +var getTxsByAccountCmd = &cobra.Command{ + Use: "gettxsbyname ", + Short: "Returns the transaction for the given account", + Long: `Returns the transaction for the given account`, + Args: cobra.RangeArgs(1, 2), + Run: func(cmd *cobra.Command, args []string) { + var ( + result interface{} + fullTx bool + ) + if len(args) > 1 { + fullTx = parseBool(args[1]) + } + + clientCall(ipcEndpoint, &result, "txpool_getTransactionsByAccount", args[0], fullTx) + printJSON(result) + }, +} + var getPendingTxsCmd = &cobra.Command{ Use: "getpending ", Short: "Returns the pending transactions that are in the transaction pool", @@ -88,6 +116,6 @@ var getPendingTxsCmd = &cobra.Command{ func init() { RootCmd.AddCommand(txpoolCommand) - txpoolCommand.AddCommand(contentCmd, statusCmd, setGasPriceCmd, getTxCmd, getPendingTxsCmd) + txpoolCommand.AddCommand(contentCmd, statusCmd, setGasPriceCmd, getTxsCmd, getTxsByAccountCmd, getPendingTxsCmd) txpoolCommand.PersistentFlags().StringVarP(&ipcEndpoint, "ipcpath", "i", defaultIPCEndpoint(params.ClientIdentifier), "IPC Endpoint path") } diff --git a/cmd/ft/utils.go b/cmd/ft/utils.go index cdab5739..5174b05d 100644 --- a/cmd/ft/utils.go +++ b/cmd/ft/utils.go @@ -101,6 +101,20 @@ var tomlSettings = toml.Config{ }, } +func clientCallRaw(endpoint string, method string, args ...interface{}) json.RawMessage { + client, err := dialRPC(ipcEndpoint) + if err != nil { + jww.ERROR.Println(err) + os.Exit(-1) + } + msg, err := client.CallRaw(method, args...) + if err != nil { + jww.ERROR.Println(err) + os.Exit(-1) + } + return msg +} + func clientCall(endpoint string, result interface{}, method string, args ...interface{}) { client, err := dialRPC(ipcEndpoint) if err != nil { diff --git a/cmd/ftfinder/root.go b/cmd/ftfinder/root.go index 229e274f..0af09024 100644 --- a/cmd/ftfinder/root.go +++ b/cmd/ftfinder/root.go @@ -27,11 +27,14 @@ import ( "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/node" "github.com/fractalplatform/fractal/p2p" + "github.com/fractalplatform/fractal/rpc" "github.com/spf13/cobra" ) var nodeConfig = node.Config{ - P2PConfig: &p2p.Config{}, + P2PConfig: &p2p.Config{}, + IPCPath: "ftfinder.ipc", + P2PNodeDatabase: "nodedb", } // RootCmd represents the base command when called without any subcommands @@ -49,19 +52,38 @@ var RootCmd = &cobra.Command{ nodeConfig.P2PConfig.GenesisHash = common.HexToHash(hexStr) nodeConfig.P2PConfig.Logger = log.New() nodeConfig.P2PConfig.NodeDatabase = nodeConfig.NodeDB() - srv := p2p.Server{ + srv := &p2p.Server{ Config: nodeConfig.P2PConfig, } for i, n := range srv.Config.BootstrapNodes { fmt.Println(i, n.String()) } - srv.DiscoverOnly() + err := srv.DiscoverOnly() + defer srv.Stop() + if err != nil { + log.Error("ftfinder start failed", "error", err) + return + } + rpcListener, rpcHandler, err := rpc.StartIPCEndpoint(nodeConfig.IPCEndpoint(), []rpc.API{ + rpc.API{ + Namespace: "p2p", + Version: "1.0", + Service: &FinderRPC{srv}, + Public: false, + }, + }) + if err != nil { + log.Error("ipc start failed", "error", err) + return + } + defer rpcHandler.Stop() + defer rpcListener.Close() + sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(sigc) <-sigc log.Info("Got interrupt, shutting down...") - srv.Stop() }, } diff --git a/cmd/ftfinder/rpc.go b/cmd/ftfinder/rpc.go new file mode 100644 index 00000000..87909077 --- /dev/null +++ b/cmd/ftfinder/rpc.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/fractalplatform/fractal/p2p/enode" +) + +type Backend interface { + SeedNodes() []*enode.Node +} + +type FinderRPC struct { + b Backend +} + +// SeedNodes returns all seed nodes. +func (fr *FinderRPC) SeedNodes() []string { + nodes := fr.b.SeedNodes() + ns := make([]string, len(nodes)) + for i, node := range nodes { + ns[i] = node.String() + } + return ns +} diff --git a/cmd/utils/version.go b/cmd/utils/version.go index abbb258f..4afadec7 100644 --- a/cmd/utils/version.go +++ b/cmd/utils/version.go @@ -48,7 +48,9 @@ func version() { fmt.Println("Go Version:", goversion) } fmt.Println("Operating System:", runtime.GOOS) - fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) + if goPath := os.Getenv("GOPATH"); goPath != "" { + fmt.Printf("GOPATH=%s\n", goPath) + } fmt.Printf("GOROOT=%s\n", runtime.GOROOT()) } @@ -59,7 +61,7 @@ func FullVersion() string { version += "+commit." + commit } if date != "" { - version += "+" + date + version += "+date." + date } return version } diff --git a/common/address.go b/common/address.go index 3eddea5b..9ec53a01 100644 --- a/common/address.go +++ b/common/address.go @@ -55,7 +55,7 @@ func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } // IsHexAddress verifies whether a string can represent a valid hex-encoded // address or not. func IsHexAddress(s string) bool { - if hasHexPrefix(s) { + if has0xPrefix(s) { s = s[2:] } return len(s) == 2*AddressLength && isHex(s) @@ -113,7 +113,8 @@ func (a *Address) SetBytes(b []byte) { copy(a[AddressLength-len(b):], b) } -// MarshalText returns the hex representation of a. +// MarshalText returns the hex representation of a. Implements encoding.TextMarshaler +// is supported by most codec implementations (e.g. for yaml or toml). func (a Address) MarshalText() ([]byte, error) { return hexutil.Bytes(a[:]).MarshalText() } @@ -140,7 +141,8 @@ func (a *UnprefixedAddress) UnmarshalText(input []byte) error { return hexutil.UnmarshalFixedUnprefixedText("UnprefixedAddress", input, a[:]) } -// MarshalText encodes the address as hex. +// MarshalText encodes the address as hex.Implements encoding.TextMarshaler +// is supported by most codec implementations (e.g. for yaml or toml). func (a UnprefixedAddress) MarshalText() ([]byte, error) { return []byte(hex.EncodeToString(a[:])), nil } diff --git a/common/address_test.go b/common/address_test.go index e3ba715c..40e9b4b9 100644 --- a/common/address_test.go +++ b/common/address_test.go @@ -17,10 +17,13 @@ package common import ( + "bytes" "encoding/json" - + "math" "math/big" "testing" + + "gopkg.in/yaml.v2" ) func TestIsHexAddress(t *testing.T) { @@ -102,6 +105,88 @@ func TestAddressHexChecksum(t *testing.T) { } } +func TestAddressConvert(t *testing.T) { + var tests = []struct { + BigInt *big.Int + HexString string + Bytes []byte + }{ + {big.NewInt(math.MaxInt64), + "0x0000000000000000000000007FFfFFFffFfFfFff", + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255, 255, 255, 255, 255, 255, 255}}, + {big.NewInt(10), + "0x000000000000000000000000000000000000000A", + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10}}, + {big.NewInt(15), + "0x000000000000000000000000000000000000000F", + []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15}}, + } + for i, test := range tests { + addr := BigToAddress(test.BigInt) + if addr.Hex() != test.HexString { + t.Errorf("test #%d: failed to match when it should (%s != %s)", i, addr.Hex(), test.HexString) + } + if !bytes.Equal(addr.Bytes(), test.Bytes) { + t.Errorf("test #%d: failed to match when it should (%x != %x)", i, addr.Bytes(), test.Bytes) + } + } +} + +func TestAddressMarshal(t *testing.T) { + testAddr := HexToAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + if marshaltext, err := yaml.Marshal(testAddr); err != nil { + t.Errorf("MarshalText err: %v", err) + } else { + target := []byte{48, 120, 53, 97, 97, 101, 98, 54, 48, 53, 51, 102, 51, 101, 57, 52, 99, 57, 98, 57, 97, 48, 57, 102, 51, 51, 54, 54, 57, 52, 51, 53, 101, 55, 101, 102, 49, 98, 101, 97, 101, 100, 10} + if !bytes.Equal(marshaltext, target) { + t.Errorf("MarshalText mismatch when it should (%x != %x)", marshaltext, target) + } + + newAddress := Address{} + if err := yaml.Unmarshal(marshaltext, &newAddress); err != nil { + t.Errorf("UnmarshalText err: %v", err) + } + if 0 != newAddress.Compare(testAddr) { + t.Errorf("UnmarshalText address mismatch") + } + } +} + +func TestUnprefixedAddressMarshal(t *testing.T) { + marshaltext := []byte{53, 97, 97, 101, 98, 54, 48, 53, 51, 102, 51, 101, 57, 52, 99, 57, 98, 57, 97, 48, 57, 102, 51, 51, 54, 54, 57, 52, 51, 53, 101, 55, 101, 102, 49, 98, 101, 97, 101, 100, 10} + unprefixedAddr := UnprefixedAddress{} + if err := yaml.Unmarshal(marshaltext, &unprefixedAddr); err != nil { + t.Errorf("UnmarshalText err: %v", err) + } + + if fetchedMarshaltext, err := yaml.Marshal(unprefixedAddr); err != nil { + t.Errorf("MarshalText err: %v", err) + } else { + if !bytes.Equal(marshaltext, fetchedMarshaltext) { + t.Errorf("MarshalText mismatch when it should (%s != %s)", marshaltext, fetchedMarshaltext) + } + } +} + +func TestMixedcaseAddress(t *testing.T) { + hexString := "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed" + originalAddr := HexToAddress(hexString) + testAddr := NewMixedcaseAddress(originalAddr) + mixedcaseRes := "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed [chksum ok]" + if mixedcaseRes != testAddr.String() { + t.Errorf("MixedcaseAddress String mismatched") + } + + mixedcaseRes = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" + if mixedcaseRes != testAddr.Original() { + t.Errorf("MixedcaseAddress Original mismatched") + } + + if 0 != testAddr.Address().Compare(originalAddr) { + t.Errorf("MixedcaseAddress address mismatch") + } +} + func BenchmarkAddressHex(b *testing.B) { testAddr := HexToAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") for n := 0; n < b.N; n++ { diff --git a/common/author.go b/common/author.go index a205e2b2..f4d71285 100644 --- a/common/author.go +++ b/common/author.go @@ -133,15 +133,7 @@ func (a *Author) decode(sa *StorageAuthor) error { } func (a *Author) MarshalJSON() ([]byte, error) { - switch aTy := a.Owner.(type) { - case Name: - return json.Marshal(&AuthorJSON{authorType: AccountNameType, OwnerStr: aTy.String(), Weight: a.Weight}) - case PubKey: - return json.Marshal(&AuthorJSON{authorType: PubKeyType, OwnerStr: aTy.String(), Weight: a.Weight}) - case Address: - return json.Marshal(&AuthorJSON{authorType: AddressType, OwnerStr: aTy.String(), Weight: a.Weight}) - } - return nil, errors.New("Author marshal failed") + return json.Marshal(&AuthorJSON{OwnerStr: a.Owner.String(), Weight: a.Weight}) } func (a *Author) UnmarshalJSON(data []byte) error { @@ -149,16 +141,7 @@ func (a *Author) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, aj); err != nil { return err } - switch aj.authorType { - case AccountNameType: - a.Owner = Name(aj.OwnerStr) - a.Weight = aj.Weight - case PubKeyType: - a.Owner = HexToPubKey(aj.OwnerStr) - a.Weight = aj.Weight - case AddressType: - a.Owner = HexToAddress(aj.OwnerStr) - a.Weight = aj.Weight - } + a.Owner = Name(aj.OwnerStr) + a.Weight = aj.Weight return nil } diff --git a/common/author_test.go b/common/author_test.go index 2963a544..643684eb 100644 --- a/common/author_test.go +++ b/common/author_test.go @@ -51,11 +51,12 @@ func TestAuthorMarshalAndUnMarshal(t *testing.T) { inputAuthor *Author }{ {&Author{Owner: Name("test"), Weight: 1}}, - {&Author{Owner: HexToPubKey("test"), Weight: 1}}, - {&Author{Owner: HexToAddress("test"), Weight: 1}}, + {&Author{Owner: HexToPubKey("123455"), Weight: 1}}, + {&Author{Owner: HexToAddress("13123123123"), Weight: 1}}, } for _, test := range tests { authorBytes, err := json.Marshal(test.inputAuthor) + if err != nil { t.Fatal(err) } diff --git a/common/bytes.go b/common/bytes.go index 880841ec..5f456ab3 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -17,11 +17,7 @@ // Package common contains various helper functions. package common -import ( - "encoding/binary" - "encoding/hex" - "io" -) +import "encoding/hex" // ToHex returns the hex representation of b, prefixed with '0x'. // For empty slices, the return value is "0x0". @@ -35,13 +31,20 @@ func ToHex(b []byte) string { return "0x" + hex } +// ToHexArray creates a array of hex-string based on []byte +func ToHexArray(b [][]byte) []string { + r := make([]string, len(b)) + for i := range b { + r[i] = ToHex(b[i]) + } + return r +} + // FromHex returns the bytes represented by the hexadecimal string s. // s may be prefixed with "0x". func FromHex(s string) []byte { - if len(s) > 1 { - if s[0:2] == "0x" || s[0:2] == "0X" { - s = s[2:] - } + if has0xPrefix(s) { + s = s[2:] } if len(s)%2 == 1 { s = "0" + s @@ -60,8 +63,8 @@ func CopyBytes(b []byte) (copiedBytes []byte) { return } -// hasHexPrefix validates str begins with '0x' or '0X'. -func hasHexPrefix(str string) bool { +// has0xPrefix validates str begins with '0x' or '0X'. +func has0xPrefix(str string) bool { return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') } @@ -104,7 +107,7 @@ func Hex2BytesFixed(str string, flen int) []byte { return h[len(h)-flen:] } hh := make([]byte, flen) - copy(hh[flen-len(h):flen], h[:]) + copy(hh[flen-len(h):flen], h) return hh } @@ -132,29 +135,13 @@ func LeftPadBytes(slice []byte, l int) []byte { return padded } -func ReadVarInt(r io.Reader) (uint64, error) { - var ( - count = make([]byte, 1) - buf []byte - err error - ) - if n, err := r.Read(count); err != nil { - return uint64(n), err - } - switch count[0] { - case 0xFD: - buf := make([]byte, 2) - _, err = io.ReadFull(r, buf) - return (uint64)(binary.LittleEndian.Uint16(buf)), err - case 0xFE: - buf = make([]byte, 4) - _, err = io.ReadFull(r, buf) - return (uint64)(binary.LittleEndian.Uint32(buf)), err - case 0xFF: - buf = make([]byte, 8) - _, err = io.ReadFull(r, buf) - return binary.LittleEndian.Uint64(buf), err - default: - return uint64(uint8(count[0])), err +// TrimLeftZeroes returns a subslice of s without leading zeroes +func TrimLeftZeroes(s []byte) []byte { + idx := 0 + for ; idx < len(s); idx++ { + if s[idx] != 0 { + break + } } + return s[idx:] } diff --git a/common/bytes_test.go b/common/bytes_test.go index a8976a94..ae48391c 100644 --- a/common/bytes_test.go +++ b/common/bytes_test.go @@ -103,3 +103,10 @@ func TestNoPrefixShortHexOddLength(t *testing.T) { t.Errorf("Expected %x got %x", expected, result) } } + +func BenchmarkCutCustomTrim(b *testing.B) { + value := HexToHash("0x01") + for i := 0; i < b.N; i++ { + TrimLeftZeroes(value[:]) + } +} diff --git a/common/gaspool_test.go b/common/gaspool_test.go index 9e13e846..cd89f9d6 100644 --- a/common/gaspool_test.go +++ b/common/gaspool_test.go @@ -17,6 +17,7 @@ package common import ( + "fmt" "math" "testing" ) @@ -55,4 +56,5 @@ func TestSubGas(t *testing.T) { if err := gaspool.SubGas(uint64(2)); err == nil || err != ErrGasLimitReached { t.Fatalf("expect ErrGasLimitReached") } + fmt.Println(gaspool) } diff --git a/common/hash.go b/common/hash.go index 64578976..68fd2a0f 100644 --- a/common/hash.go +++ b/common/hash.go @@ -20,7 +20,6 @@ import ( "encoding/hex" "fmt" "math/big" - "math/rand" "reflect" "github.com/ethereum/go-ethereum/common/hexutil" @@ -87,7 +86,8 @@ func (h *Hash) UnmarshalJSON(input []byte) error { return hexutil.UnmarshalFixedJSON(hashT, input, h[:]) } -// MarshalText returns the hex representation of h. +// MarshalText returns the hex representation of h.Implements encoding.TextMarshaler +// is supported by most codec implementations (e.g. for yaml or toml). func (h Hash) MarshalText() ([]byte, error) { return hexutil.Bytes(h[:]).MarshalText() } @@ -102,15 +102,6 @@ func (h *Hash) SetBytes(b []byte) { copy(h[HashLength-len(b):], b) } -// Generate implements testing/quick.Generator. -func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { - m := rand.Intn(len(h)) - for i := len(h) - 1; i > m; i-- { - h[i] = byte(rand.Uint32()) - } - return reflect.ValueOf(h) -} - // UnprefixedHash allows marshaling a Hash without 0x prefix. type UnprefixedHash Hash @@ -119,7 +110,8 @@ func (h *UnprefixedHash) UnmarshalText(input []byte) error { return hexutil.UnmarshalFixedUnprefixedText("UnprefixedHash", input, h[:]) } -// MarshalText encodes the hash as hex. +// MarshalText encodes the hash as hex. Implements encoding.TextMarshaler +// is supported by most codec implementations (e.g. for yaml or toml). func (h UnprefixedHash) MarshalText() ([]byte, error) { return []byte(hex.EncodeToString(h[:])), nil } diff --git a/common/path.go b/common/path.go deleted file mode 100644 index bd7bf44b..00000000 --- a/common/path.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The Fractal Team Authors -// This file is part of the fractal project. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package common - -import ( - "os" - "path/filepath" -) - -// FileExist checks if a file exists at filePath. -func FileExist(filePath string) bool { - _, err := os.Stat(filePath) - if err != nil && os.IsNotExist(err) { - return false - } - return true -} - -// AbsolutePath returns datadir + filename, or filename if it is absolute. -func AbsolutePath(datadir string, filename string) string { - if filepath.IsAbs(filename) { - return filename - } - return filepath.Join(datadir, filename) -} diff --git a/common/pubkey.go b/common/pubkey.go index 1251ce2a..e87f28ba 100644 --- a/common/pubkey.go +++ b/common/pubkey.go @@ -42,7 +42,7 @@ func BytesToPubKey(b []byte) PubKey { // IsHexPubKey verifies whether a string can represent a valid hex-encoded // PubKey or not. func IsHexPubKey(s string) bool { - if hasHexPrefix(s) { + if has0xPrefix(s) { s = s[2:] } return len(s) == 2*(PubKeyLength) && isHex(s) @@ -73,7 +73,8 @@ func (p PubKey) String() string { return p.Hex() } -// MarshalText returns the hex representation of a. +// MarshalText returns the hex representation of p. Implements encoding.TextMarshaler +// is supported by most codec implementations (e.g. for yaml or toml). func (p PubKey) MarshalText() ([]byte, error) { return hexutil.Bytes(p[:]).MarshalText() } @@ -94,3 +95,6 @@ func (p *PubKey) UnmarshalJSON(input []byte) error { func (p PubKey) Compare(x PubKey) int { return bytes.Compare(p.Bytes(), x.Bytes()) } + +// EmptyPubKey empty +var EmptyPubKey PubKey diff --git a/consensus/dpos/api.go b/consensus/dpos/api.go index 476cdaf9..88609418 100644 --- a/consensus/dpos/api.go +++ b/consensus/dpos/api.go @@ -227,6 +227,7 @@ func (api *API) BrowserAllEpoch() (interface{}, error) { if err != nil { return nil, err } + for { data := &Epoch{} timestamp := sys.config.epochTimeStamp(epochNumber) @@ -234,18 +235,36 @@ func (api *API) BrowserAllEpoch() (interface{}, error) { if err != nil { return nil, err } - if sys.config.epoch(sys.config.ReferenceTime) == gstate.PreEpoch { - timestamp = sys.config.epochTimeStamp(gstate.PreEpoch) + if gstate.PreEpoch == gstate.Epoch { + timestamp = sys.config.ReferenceTime } - data.Start = timestamp / 1000000000 data.Epoch = epochNumber epochs.Data = append(epochs.Data, data) - if epochNumber == 1 { + if gstate.PreEpoch == gstate.Epoch { break } epochNumber = gstate.PreEpoch } + // for { + // data := &Epoch{} + // timestamp := sys.config.epochTimeStamp(epochNumber) + // gstate, err := sys.GetState(epochNumber) + // if err != nil { + // return nil, err + // } + // if sys.config.epoch(sys.config.ReferenceTime) == gstate.PreEpoch { + // timestamp = sys.config.epochTimeStamp(gstate.PreEpoch) + // } + + // data.Start = timestamp / 1000000000 + // data.Epoch = epochNumber + // epochs.Data = append(epochs.Data, data) + // if epochNumber == 1 { + // break + // } + // epochNumber = gstate.PreEpoch + // } return epochs, nil } @@ -326,7 +345,7 @@ func (api *API) BrowserEpochRecord(reqEpochNumber uint64) (interface{}, error) { } } candidateInfo.Holder = balance.String() - // candidateInfo.URL = tmp.URL + // candidateInfo.Info = tmp.Info candidateInfos.Data = append(candidateInfos.Data, candidateInfo) } @@ -415,7 +434,7 @@ func (api *API) BrowserVote(reqEpochNumber uint64) (interface{}, error) { candidateInfo.Quantity = c.Quantity.Mul(c.Quantity, api.dpos.config.unitStake()).String() candidateInfo.TotalQuantity = c.TotalQuantity.String() - // candidateInfo.URL = c.URL + // candidateInfo.Info = c.Info candidateInfo.Holder = balance.String() tmp, err := sys.GetCandidate(req, c.Name) @@ -435,28 +454,37 @@ func (api *API) BrowserVote(reqEpochNumber uint64) (interface{}, error) { // BrowserAllEpoch2 get all epoch info for browser api func (api *API) BrowserAllEpoch2() (interface{}, error) { - epochs := Epochs{} - epochs.Data = make([]*Epoch, 0) + epochs := VoteEpochs{} + epochs.Data = make([]*VoteEpoch, 0) epochNumber, _ := api.epoch(api.chain.CurrentHeader().Number.Uint64()) sys, err := api.system() if err != nil { return nil, err } for { - data := &Epoch{} + data := &VoteEpoch{} timestamp := sys.config.epochTimeStamp(epochNumber) gstate, err := sys.GetState(epochNumber) if err != nil { return nil, err } - if sys.config.epoch(sys.config.ReferenceTime) == gstate.PreEpoch { - timestamp = sys.config.epochTimeStamp(gstate.PreEpoch) + if gstate.PreEpoch == gstate.Epoch { + timestamp = sys.config.ReferenceTime + } + + dataEpoch, err := sys.GetState(gstate.PreEpoch) + if err != nil { + return nil, err + } + + if dataEpoch.Dpos { + data.Dpos = 1 } data.Start = timestamp / 1000000000 data.Epoch = epochNumber + 1 epochs.Data = append(epochs.Data, data) - if epochNumber == 1 { + if gstate.PreEpoch == gstate.Epoch { break } epochNumber = gstate.PreEpoch @@ -542,7 +570,7 @@ func (api *API) VoterInfo(reqEpochNumber uint64) (interface{}, error) { tmp := c.Quantity.Mul(c.Quantity, api.dpos.config.unitStake()) voter.Quantity = tmp.Div(tmp, declimsBigInt).String() voter.TotalQuantity = c.TotalQuantity.String() - voter.URL = c.URL + voter.Info = c.Info voter.Holder = balance.Div(balance, declimsBigInt).String() data = append(data, voter) diff --git a/consensus/dpos/config_test.go b/consensus/dpos/config_test.go index 146f643f..9d51e479 100644 --- a/consensus/dpos/config_test.go +++ b/consensus/dpos/config_test.go @@ -51,6 +51,10 @@ func TestConfig(t *testing.T) { panic(fmt.Errorf("Config consensusSize mismatch")) } + if 3 != DefaultConfig.consensusSize() { + panic(fmt.Errorf("Config Cache consensusSize mismatch")) + } + if 0 != DefaultConfig.slot(1567591745) { panic(fmt.Errorf("Config slot mismatch")) } @@ -63,6 +67,10 @@ func TestConfig(t *testing.T) { panic(fmt.Errorf("Config getoffset mismatch")) } + if 0 != DefaultConfig.getoffset(1567591745, 2) { + panic(fmt.Errorf("Config getoffset mismatch")) + } + if 15639786 != DefaultConfig.epoch(1567591745) { panic(fmt.Errorf("Config epoch mismatch")) } @@ -71,7 +79,11 @@ func TestConfig(t *testing.T) { panic(fmt.Errorf("Config epochTimeStamp mismatch")) } - if 0 != DefaultConfig.shouldCounter(1567591745, 1567591745) { + if 0 != DefaultConfig.shouldCounter(1567591745000, 1567591745000) { + panic(fmt.Errorf("Config epochTimeStamp mismatch")) + } + + if 3 != DefaultConfig.shouldCounter(1521370523000, 1567591745000) { panic(fmt.Errorf("Config epochTimeStamp mismatch")) } @@ -86,4 +98,9 @@ func TestConfig(t *testing.T) { if err := DefaultConfig.IsValid(); err != nil { panic(fmt.Errorf("Config IsValid err %v", err)) } + + DefaultConfig.epochInter.Store(uint64(1070000)) + if err := DefaultConfig.IsValid(); err == nil { + panic(fmt.Errorf("Config IsValid err %v", err)) + } } diff --git a/consensus/dpos/database.go b/consensus/dpos/database.go index d9347550..5dea20e9 100644 --- a/consensus/dpos/database.go +++ b/consensus/dpos/database.go @@ -23,6 +23,7 @@ import ( "math/big" "strings" + "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/types" ) @@ -54,12 +55,14 @@ type IDB interface { GetTakeOver() (uint64, error) Undelegate(string, *big.Int) (*types.Action, error) - IncAsset2Acct(string, string, *big.Int) (*types.Action, error) + IncAsset2Acct(string, string, *big.Int, uint64) (*types.Action, error) GetBalanceByTime(name string, timestamp uint64) (*big.Int, error) GetCandidateInfoByTime(epoch uint64, name string, timestamp uint64) (*CandidateInfo, error) + + CanMine(name string, pub []byte) error } -// CandidateType candiate status +// CandidateType candidate status type CandidateType uint64 const ( @@ -75,7 +78,8 @@ const ( Unkown ) -// MarshalText returns the hex representation of a. +// MarshalText returns the hex representation of a. Implements encoding.TextMarshaler +// is supported by most codec implementations (e.g. for yaml or toml). func (t CandidateType) MarshalText() ([]byte, error) { return t.MarshalJSON() } @@ -127,7 +131,7 @@ func (t *CandidateType) UnmarshalJSON(data []byte) error { type CandidateInfo struct { Epoch uint64 `json:"epoch"` Name string `json:"name"` // candidate name - URL string `json:"url"` // candidate url + Info string `json:"info"` // candidate url Quantity *big.Int `json:"quantity"` // candidate stake quantity TotalQuantity *big.Int `json:"totalQuantity"` // candidate total stake quantity Number uint64 `json:"number"` // timestamp @@ -136,19 +140,21 @@ type CandidateInfo struct { Type CandidateType `json:"type"` PrevKey string `json:"-"` NextKey string `json:"-"` + PubKey common.PubKey `json:"pubkey" rlp:"-"` } func (candidateInfo *CandidateInfo) copy() *CandidateInfo { return &CandidateInfo{ Epoch: candidateInfo.Epoch, Name: candidateInfo.Name, - URL: candidateInfo.URL, + Info: candidateInfo.Info, Quantity: candidateInfo.Quantity, TotalQuantity: candidateInfo.TotalQuantity, Number: candidateInfo.Number, Counter: candidateInfo.Counter, ActualCounter: candidateInfo.ActualCounter, Type: candidateInfo.Type, + PubKey: candidateInfo.PubKey, } } @@ -209,7 +215,7 @@ type CandidateInfoForBrowser struct { ActualCounter uint64 `json:"actualCounter"` NowCounter uint64 `json:"nowShouldCounter"` NowActualCounter uint64 `json:"nowActualCounter"` - // URL string `json:"url"` + // Info string `json:"info"` // Status uint64 `json:"status"` //0:die 1:activate 2:spare } @@ -218,7 +224,7 @@ type VoterInfoFractal struct { Holder string `json:"holder"` Quantity string `json:"quantity"` TotalQuantity string `json:"totalQuantity"` - URL string `json:"url"` + Info string `json:"info"` State uint64 `json:"state"` Vote uint64 `json:"vote"` CanVote bool `json:"canVote"` @@ -238,6 +244,18 @@ type Epoch struct { Epoch uint64 `json:"epoch"` } +// VoteEpochs array of epcho +type VoteEpochs struct { + Data []*VoteEpoch `json:"data"` +} + +// VoteEpoch timestamp & epoch number & dpos status +type VoteEpoch struct { + Start uint64 `json:"start"` + Epoch uint64 `json:"epoch"` + Dpos uint64 `json:"dpos"` +} + func (prods CandidateInfoArray) Len() int { return len(prods) } diff --git a/consensus/dpos/database_test.go b/consensus/dpos/database_test.go index 4703ec78..768e4d2e 100644 --- a/consensus/dpos/database_test.go +++ b/consensus/dpos/database_test.go @@ -20,16 +20,18 @@ import ( "fmt" "math/big" "testing" + + yaml "gopkg.in/yaml.v2" ) -func TestCandidateType(t *testing.T) { +func TestCandidateMarshalText(t *testing.T) { typeList := []CandidateType{Normal, Freeze, Black, Jail, Unkown} for _, t := range typeList { - if text, err := t.MarshalText(); err != nil { + if text, err := yaml.Marshal(t); err != nil { panic(fmt.Errorf("MarshalText --- %v", err)) } else { var newCt CandidateType - if err := newCt.UnmarshalText(text); err != nil { + if err := yaml.Unmarshal(text, &newCt); err != nil { panic(fmt.Errorf("UnmarshalText --- %v", err)) } else { if newCt != t { @@ -45,7 +47,7 @@ func TestCandidateInfo(t *testing.T) { candidateInfo := &CandidateInfo{ Epoch: 1, Name: "candidate1", - URL: "", + Info: "", Quantity: big.NewInt(100), TotalQuantity: big.NewInt(100), Number: 1, diff --git a/consensus/dpos/dpos.go b/consensus/dpos/dpos.go index a47f2e01..91676c63 100644 --- a/consensus/dpos/dpos.go +++ b/consensus/dpos/dpos.go @@ -86,20 +86,20 @@ func (s *stateDB) Undelegate(to string, amount *big.Int) (*types.Action, error) } return action, accountDB.TransferAsset(common.StrToName(s.name), common.StrToName(to), s.assetid, amount) } -func (s *stateDB) IncAsset2Acct(from string, to string, amount *big.Int) (*types.Action, error) { +func (s *stateDB) IncAsset2Acct(from string, to string, amount *big.Int, forkID uint64) (*types.Action, error) { action := types.NewAction(types.IncreaseAsset, common.StrToName(s.name), common.StrToName(to), 0, s.assetid, 0, amount, nil, nil) accountDB, err := accountmanager.NewAccountManager(s.state) if err != nil { return action, err } - return action, accountDB.IncAsset2Acct(common.StrToName(from), common.StrToName(to), s.assetid, amount) + return action, accountDB.IncAsset2Acct(common.StrToName(from), common.StrToName(to), s.assetid, amount, forkID) } -func (s *stateDB) IsValidSign(name string, pubkey []byte) bool { +func (s *stateDB) IsValidSign(name string, pubkey []byte) error { accountDB, err := accountmanager.NewAccountManager(s.state) if err != nil { - return false + return err } - return accountDB.IsValidSign(common.StrToName(name), common.BytesToPubKey(pubkey)) == nil + return accountDB.IsValidSign(common.StrToName(name), common.BytesToPubKey(pubkey)) } func (s *stateDB) GetBalanceByTime(name string, timestamp uint64) (*big.Int, error) { accountDB, err := accountmanager.NewAccountManager(s.state) @@ -135,7 +135,7 @@ func Genesis(cfg *Config, state *state.StateDB, timestamp uint64, number uint64) if err := sys.SetCandidate(&CandidateInfo{ Epoch: epoch, Name: cfg.SystemName, - URL: cfg.SystemURL, + Info: cfg.SystemURL, Quantity: big.NewInt(0), TotalQuantity: big.NewInt(0), Number: number, @@ -531,7 +531,7 @@ func (dpos *Dpos) finalize0(chain consensus.IChainReader, header *types.Header, counter := int64(0) extraReward := new(big.Int).Mul(dpos.config.extraBlockReward(), big.NewInt(counter)) reward := new(big.Int).Add(dpos.config.blockReward(), extraReward) - sys.IncAsset2Acct(dpos.config.SystemName, header.Coinbase.String(), reward) + sys.IncAsset2Acct(dpos.config.SystemName, header.Coinbase.String(), reward, header.CurForkID()) blk := types.NewBlock(header, txs, receipts) @@ -689,7 +689,7 @@ func (dpos *Dpos) finalize1(chain consensus.IChainReader, header *types.Header, extraCounter := int64(0) extraReward := new(big.Int).Mul(dpos.config.extraBlockReward(), big.NewInt(extraCounter)) reward := new(big.Int).Add(dpos.config.blockReward(), extraReward) - sys.IncAsset2Acct(dpos.config.SystemName, header.Coinbase.String(), reward) + sys.IncAsset2Acct(dpos.config.SystemName, header.Coinbase.String(), reward, header.CurForkID()) blk := types.NewBlock(header, txs, receipts) // first hard fork at a specific number @@ -789,20 +789,6 @@ func (dpos *Dpos) IsValidateCandidate(chain consensus.IChainReader, parent *type return errInvalidMintBlockTime } - db := &stateDB{ - name: dpos.config.AccountName, - state: state, - } - has := false - for _, pubkey := range pubkeys { - if db.IsValidSign(candidate, pubkey) { - has = true - } - } - if !has { - return ErrIllegalCandidatePubKey - } - sys := NewSystem(state, dpos.config) pepoch := dpos.config.epoch(parent.Time.Uint64()) gstate, err := sys.GetState(pepoch) @@ -1015,6 +1001,16 @@ func (dpos *Dpos) IsValidateCandidate(chain consensus.IChainReader, parent *type if strings.Compare(tname, candidate) != 0 { return fmt.Errorf("%v %v, except %v %v(%v) index %v (%v epoch) ", errInvalidBlockCandidate, candidate, tname, pstate.ActivatedCandidateSchedule, pstate.UsingCandidateIndexSchedule, offset, pstate.Epoch) } + + has := false + for _, pubkey := range pubkeys { + if sys.CanMine(candidate, pubkey) == nil { + has = true + } + } + if !has { + return ErrIllegalCandidatePubKey + } return nil } diff --git a/consensus/dpos/ldb.go b/consensus/dpos/ldb.go index da30689e..6aad1821 100644 --- a/consensus/dpos/ldb.go +++ b/consensus/dpos/ldb.go @@ -17,12 +17,14 @@ package dpos import ( + "bytes" "encoding/binary" "encoding/hex" "fmt" "math/big" "strings" + "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/types" "github.com/fractalplatform/fractal/utils/rlp" ) @@ -34,9 +36,9 @@ type IDatabase interface { Delete(key string) error Undelegate(string, *big.Int) (*types.Action, error) - IncAsset2Acct(string, string, *big.Int) (*types.Action, error) + IncAsset2Acct(string, string, *big.Int, uint64) (*types.Action, error) GetBalanceByTime(name string, timestamp uint64) (*big.Int, error) - + IsValidSign(name string, pubkey []byte) error GetSnapshot(key string, timestamp uint64) ([]byte, error) } @@ -48,6 +50,9 @@ var ( // ActivatedCandidateKeyPrefix candidateInfo ActivatedCandidateKeyPrefix = "ap" + // CandidatePubKeyPrefix candidateInfo + CandidatePubKeyPrefix = "pk" + // VoterKeyPrefix voterInfo VoterKeyPrefix = "v" // VoterHead head @@ -80,6 +85,20 @@ func NewLDB(db IDatabase) (*LDB, error) { return ldb, nil } +// CanMine allow mining block +func (db *LDB) CanMine(name string, pub []byte) error { + pubkey := strings.Join([]string{CandidatePubKeyPrefix, fmt.Sprintf("%s", name)}, Separator) + if val, _ := db.Get(pubkey); val != nil { + if bytes.Compare(common.EmptyPubKey.Bytes(), val) != 0 { + if bytes.Compare(pub, val) == 0 { + return nil + } + return fmt.Errorf("need pubkey %s", common.BytesToPubKey(val).String()) + } + } + return db.IsValidSign(name, pub) +} + // SetCandidate update candidate info func (db *LDB) SetCandidate(candidate *CandidateInfo) error { if candidate.Name != CandidateHead && len(candidate.PrevKey) == 0 && len(candidate.NextKey) == 0 { @@ -119,6 +138,18 @@ func (db *LDB) SetCandidate(candidate *CandidateInfo) error { } else if err := db.Put(key, val); err != nil { return err } + + if common.EmptyPubKey.Compare(candidate.PubKey) != 0 { + pubkey := strings.Join([]string{CandidatePubKeyPrefix, fmt.Sprintf("%s", candidate.Name)}, Separator) + if err := db.Put(pubkey, candidate.PubKey.Bytes()); err != nil { + return err + } + } else { + pubkey := strings.Join([]string{CandidatePubKeyPrefix, fmt.Sprintf("%s", candidate.Name)}, Separator) + if err := db.Delete(pubkey); err != nil { + return err + } + } return nil } @@ -155,6 +186,11 @@ func (db *LDB) DelCandidate(epoch uint64, name string) error { if err := db.Delete(key); err != nil { return err } + pubkey := strings.Join([]string{CandidatePubKeyPrefix, fmt.Sprintf("%s", name)}, Separator) + if err := db.Delete(pubkey); err != nil { + return err + } + head, err := db.GetCandidate(epoch, CandidateHead) if err != nil { return err @@ -174,6 +210,12 @@ func (db *LDB) GetCandidate(epoch uint64, name string) (*CandidateInfo, error) { } else if err := rlp.DecodeBytes(val, candidateInfo); err != nil { return nil, err } + + pubkey := strings.Join([]string{CandidatePubKeyPrefix, fmt.Sprintf("%s", name)}, Separator) + if val, _ := db.Get(pubkey); val != nil { + candidateInfo.PubKey.SetBytes(val) + } + return candidateInfo, nil } diff --git a/consensus/dpos/ldb_test.go b/consensus/dpos/ldb_test.go index 4144e6ad..626efa7e 100644 --- a/consensus/dpos/ldb_test.go +++ b/consensus/dpos/ldb_test.go @@ -61,7 +61,7 @@ func (ldb *levelDB) Delegate(string, *big.Int) error { func (ldb *levelDB) Undelegate(string, *big.Int) (*types.Action, error) { return nil, nil } -func (ldb *levelDB) IncAsset2Acct(string, string, *big.Int) (*types.Action, error) { +func (ldb *levelDB) IncAsset2Acct(string, string, *big.Int, uint64) (*types.Action, error) { return nil, nil } func (ldb *levelDB) GetSnapshot(string, uint64) ([]byte, error) { @@ -70,6 +70,11 @@ func (ldb *levelDB) GetSnapshot(string, uint64) ([]byte, error) { func (ldb *levelDB) GetBalanceByTime(name string, timestamp uint64) (*big.Int, error) { return new(big.Int).Mul(big.NewInt(1000000000), DefaultConfig.decimals()), nil } + +func (ldb *levelDB) IsValidSign(name string, pubkey []byte) error { + return nil +} + func newTestLDB() (*levelDB, func()) { dirname, err := ioutil.TempDir(os.TempDir(), "dpos_test_") if err != nil { @@ -119,7 +124,7 @@ func TestLDBCandidate(t *testing.T) { candidateInfo := &CandidateInfo{ Epoch: uint64(index), Name: candidate, - URL: fmt.Sprintf("www.%v.com", candidate), + Info: fmt.Sprintf("www.%v.com", candidate), Quantity: big.NewInt(0), TotalQuantity: big.NewInt(0), } @@ -281,7 +286,7 @@ func TestLDBVoter(t *testing.T) { candidateInfo := &CandidateInfo{ Epoch: 0, Name: candidate, - URL: fmt.Sprintf("www.%v.com", candidate), + Info: fmt.Sprintf("www.%v.com", candidate), Quantity: big.NewInt(0), TotalQuantity: big.NewInt(0), } diff --git a/consensus/dpos/processor.go b/consensus/dpos/processor.go index 68dfa4bc..b6101c03 100644 --- a/consensus/dpos/processor.go +++ b/consensus/dpos/processor.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/fractalplatform/fractal/accountmanager" + "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/utils/rlp" "github.com/fractalplatform/fractal/params" @@ -31,12 +32,17 @@ import ( // RegisterCandidate candidate info type RegisterCandidate struct { - URL string + Info string } // UpdateCandidate candidate info type UpdateCandidate struct { - URL string + Info string +} + +// UpdateCandidatePubKey candidate info +type UpdateCandidatePubKey struct { + PubKey common.PubKey } // VoteCandidate vote info @@ -66,7 +72,7 @@ func (dpos *Dpos) ProcessAction(fid uint64, number uint64, chainCfg *params.Chai } func (dpos *Dpos) processAction(fid uint64, number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) { - if err := action.Check(chainCfg); err != nil { + if err := action.Check(fid, chainCfg); err != nil { return nil, err } sys := NewSystem(state, dpos.config) @@ -95,7 +101,7 @@ func (dpos *Dpos) processAction(fid uint64, number uint64, chainCfg *params.Chai if err := rlp.DecodeBytes(action.Data(), &arg); err != nil { return nil, err } - if err := sys.RegCandidate(epoch, action.Sender().String(), arg.URL, action.Value(), number, fid); err != nil { + if err := sys.RegCandidate(epoch, action.Sender().String(), arg.Info, action.Value(), number, fid); err != nil { return nil, err } case types.UpdateCandidate: @@ -108,9 +114,25 @@ func (dpos *Dpos) processAction(fid uint64, number uint64, chainCfg *params.Chai if err := rlp.DecodeBytes(action.Data(), &arg); err != nil { return nil, err } - if err := sys.UpdateCandidate(epoch, action.Sender().String(), arg.URL, action.Value(), number, fid); err != nil { + if err := sys.UpdateCandidate(epoch, action.Sender().String(), arg.Info, action.Value(), number, fid); err != nil { + return nil, err + } + case types.UpdateCandidatePubKey: + if fid >= params.ForkID4 { + arg := &UpdateCandidatePubKey{} + if err := rlp.DecodeBytes(action.Data(), &arg); err != nil { + return nil, err + } + candidate, err := sys.GetCandidate(epoch, action.Sender().String()) + if err != nil { + return nil, err + } + candidate.PubKey.SetBytes(arg.PubKey.Bytes()) + + err = sys.SetCandidate(candidate) return nil, err } + return nil, accountmanager.ErrUnKnownTxType case types.UnregCandidate: if strings.Compare(action.Sender().String(), dpos.config.SystemName) == 0 { return nil, fmt.Errorf("no permission") diff --git a/consensus/dpos/vote.go b/consensus/dpos/vote.go index c7319659..e7ae3e86 100644 --- a/consensus/dpos/vote.go +++ b/consensus/dpos/vote.go @@ -96,7 +96,7 @@ func (sys *System) RegCandidate(epoch uint64, candidate string, url string, stak prod = &CandidateInfo{ Epoch: epoch, Name: candidate, - URL: url, + Info: url, Quantity: big.NewInt(0), TotalQuantity: big.NewInt(0), Number: number, @@ -125,9 +125,9 @@ func (sys *System) RegCandidate(epoch uint64, candidate string, url string, stak } // UpdateCandidate update a candidate -func (sys *System) UpdateCandidate(epoch uint64, candidate string, url string, nstake *big.Int, number uint64, fid uint64) error { +func (sys *System) UpdateCandidate(epoch uint64, candidate string, info string, nstake *big.Int, number uint64, fid uint64) error { // url validity - if uint64(len(url)) > sys.config.MaxURLLen { + if uint64(len(info)) > sys.config.MaxURLLen { return fmt.Errorf("invalid url (too long, max %v)", sys.config.MaxURLLen) } @@ -185,7 +185,7 @@ func (sys *System) UpdateCandidate(epoch uint64, candidate string, url string, n // }) // } - prod.URL = url + prod.Info = info prod.Quantity = new(big.Int).Add(prod.Quantity, q) prod.TotalQuantity = new(big.Int).Add(prod.TotalQuantity, q) prod.Number = number @@ -688,6 +688,8 @@ func (sys *System) UpdateElectedCandidates1(pepoch uint64, epoch uint64, number activatedTotalQuantity = new(big.Int).Add(activatedTotalQuantity, candidateInfo.TotalQuantity) } } + gstate.ActivatedCandidateSchedule = activatedCandidateSchedule + gstate.ActivatedTotalQuantity = activatedTotalQuantity } else { tstate := &GlobalState{ Epoch: math.MaxUint64, @@ -733,10 +735,11 @@ func (sys *System) UpdateElectedCandidates1(pepoch uint64, epoch uint64, number index++ } } + gstate.ActivatedCandidateSchedule = activatedCandidateSchedule + gstate.ActivatedTotalQuantity = activatedTotalQuantity } } - gstate.ActivatedCandidateSchedule = activatedCandidateSchedule - gstate.ActivatedTotalQuantity = activatedTotalQuantity + if err := sys.SetState(gstate); err != nil { return err } diff --git a/consensus/miner/miner.go b/consensus/miner/miner.go index 4664775b..782ea9e1 100644 --- a/consensus/miner/miner.go +++ b/consensus/miner/miner.go @@ -101,7 +101,6 @@ func (miner *Miner) Start(force bool) bool { // Stop stop worker func (miner *Miner) Stop() bool { if !atomic.CompareAndSwapInt32(&miner.mining, 2, 3) { - log.Error("miner already stopped") return false } log.Info("Stopping mining operation") diff --git a/consensus/miner/worker.go b/consensus/miner/worker.go index 185ad7cc..cd0227bb 100644 --- a/consensus/miner/worker.go +++ b/consensus/miner/worker.go @@ -28,7 +28,6 @@ import ( "time" "github.com/ethereum/go-ethereum/log" - "github.com/fractalplatform/fractal/accountmanager" "github.com/fractalplatform/fractal/blockchain" "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/consensus" @@ -121,23 +120,20 @@ func (worker *Worker) start(force bool) { } func (worker *Worker) mintLoop() { - dpos, ok := worker.Engine().(*dpos.Dpos) + cdpos, ok := worker.Engine().(*dpos.Dpos) if !ok { panic("only support dpos engine") } - dpos.SetSignFn(func(content []byte, state *state.StateDB) ([]byte, error) { - accountDB, err := accountmanager.NewAccountManager(state) - if err != nil { - return nil, err - } + cdpos.SetSignFn(func(content []byte, state *state.StateDB) ([]byte, error) { + sys := dpos.NewSystem(state, cdpos.Config()) for index, privKey := range worker.privKeys { - if err := accountDB.IsValidSign(common.StrToName(worker.coinbase), common.BytesToPubKey(worker.pubKeys[index])); err == nil { + if err := sys.CanMine(worker.coinbase, worker.pubKeys[index]); err == nil { return crypto.Sign(content, privKey) } } return nil, fmt.Errorf("not found match private key for sign") }) - interval := int64(dpos.BlockInterval()) + interval := int64(cdpos.BlockInterval()) c := make(chan time.Time) to := time.Now() worker.utimerTo(to.Add(time.Duration(interval-(to.UnixNano()%interval))), c) @@ -156,7 +152,7 @@ func (worker *Worker) mintLoop() { } worker.wgWork.Wait() worker.quitWork = make(chan struct{}) - timestamp := int64(dpos.Slot(uint64(now.UnixNano()))) + timestamp := int64(cdpos.Slot(uint64(now.UnixNano()))) worker.wg.Add(1) worker.wgWork.Add(1) go func(quit chan struct{}) { @@ -240,6 +236,9 @@ func (worker *Worker) setDelayDuration(delay uint64) error { } func (worker *Worker) setCoinbase(name string, privKeys []*ecdsa.PrivateKey) { + state, _ := worker.StateAt(worker.CurrentHeader().Root) + cdpos := worker.Engine().(*dpos.Dpos) + sys := dpos.NewSystem(state, cdpos.Config()) worker.mu.Lock() defer worker.mu.Unlock() worker.coinbase = name @@ -247,7 +246,11 @@ func (worker *Worker) setCoinbase(name string, privKeys []*ecdsa.PrivateKey) { worker.pubKeys = nil for index, privkey := range privKeys { pubkey := crypto.FromECDSAPub(&privkey.PublicKey) - log.Info("setCoinbase", "coinbase", name, fmt.Sprintf("pubKey_%03d", index), common.BytesToPubKey(pubkey).String()) + if err := sys.CanMine(name, pubkey); err == nil { + log.Info("setCoinbase[valid]", "coinbase", name, fmt.Sprintf("pubKey_%03d", index), common.BytesToPubKey(pubkey).String()) + } else { + log.Warn("setCoinbase[invalid]", "coinbase", name, fmt.Sprintf("pubKey_%03d", index), common.BytesToPubKey(pubkey).String(), "detail", err) + } worker.pubKeys = append(worker.pubKeys, pubkey) } } @@ -364,6 +367,9 @@ func (worker *Worker) commitTransactions(work *Work, txs *types.TransactionsByPr var coalescedLogs []*types.Log endTimeStamp := work.currentHeader.Time.Uint64() + interval - 2*interval/5 endTime := time.Unix((int64)(endTimeStamp)/(int64)(time.Second), (int64)(endTimeStamp)%(int64)(time.Second)) + t := work.currentHeader.Time.Uint64() + s := worker.Config().SnapshotInterval * uint64(time.Millisecond) + isSnapshot := t%s == 0 for { select { case <-worker.quit: @@ -391,11 +397,22 @@ func (worker *Worker) commitTransactions(work *Work, txs *types.TransactionsByPr action := tx.GetActions()[0] + if isSnapshot { + if action.Type() == types.RegCandidate || + action.Type() == types.VoteCandidate { + log.Trace("Skipping regcandidate transaction when snapshot block", "hash", tx.Hash()) + txs.Pop() + continue + } + } + if strings.Compare(work.currentHeader.Coinbase.String(), worker.Config().SysName) != 0 { switch action.Type() { case types.KickedCandidate: fallthrough case types.ExitTakeOver: + log.Trace("Skipping system transaction when not take over", "hash", tx.Hash()) + txs.Pop() continue default: } diff --git a/event/router.go b/event/router.go index 90063ccf..8c4ee4ea 100644 --- a/event/router.go +++ b/event/router.go @@ -195,7 +195,7 @@ func (router *Router) StationUnregister(station Station) { router.stationMutex.Lock() delete(router.stations, station.Name()) router.stationMutex.Unlock() - if station.IsRemote() { + if station.IsRemote() && !station.IsBroadcast() { router.eval.unregister(station) } } diff --git a/ftservice/apibackend.go b/ftservice/apibackend.go index aa75581e..2adf017a 100644 --- a/ftservice/apibackend.go +++ b/ftservice/apibackend.go @@ -336,6 +336,16 @@ func (b *APIBackend) RemoveTrustedPeer(url string) error { return err } +// SeedNodes returns all seed nodes. +func (b *APIBackend) SeedNodes() []string { + nodes := b.ftservice.p2pServer.SeedNodes() + ns := make([]string, len(nodes)) + for i, node := range nodes { + ns[i] = node.String() + } + return ns +} + // PeerCount returns the number of connected peers. func (b *APIBackend) PeerCount() int { return b.ftservice.p2pServer.PeerCount() diff --git a/ftservice/ftservice.go b/ftservice/ftservice.go index c3fa5f14..2a52d146 100644 --- a/ftservice/ftservice.go +++ b/ftservice/ftservice.go @@ -82,8 +82,6 @@ func New(ctx *node.ServiceContext, config *Config) (*FtService, error) { if err != nil { return nil, err } - // used to generate MagicNetID - ftservice.p2pServer.GenesisHash = ftservice.blockchain.Genesis().Hash() // txpool if config.TxPool.Journal != "" { diff --git a/go.mod b/go.mod index d0980cba..3ca38a80 100644 --- a/go.mod +++ b/go.mod @@ -32,4 +32,5 @@ require ( gopkg.in/fatih/set.v0 v0.2.1 gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce + gopkg.in/yaml.v2 v2.2.2 ) diff --git a/p2p/dial.go b/p2p/dial.go index 1270dca6..192b8299 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -89,6 +89,7 @@ type discoverTable interface { Resolve(*enode.Node) *enode.Node LookupRandom() []*enode.Node ReadRandomNodes([]*enode.Node) int + SeedNodes() []*enode.Node } // the dial history remembers recent dials. diff --git a/p2p/dial_test.go b/p2p/dial_test.go index 056f370e..3a4670ab 100644 --- a/p2p/dial_test.go +++ b/p2p/dial_test.go @@ -85,6 +85,7 @@ func (t fakeTable) Close() {} func (t fakeTable) LookupRandom() []*enode.Node { return nil } func (t fakeTable) Resolve(*enode.Node) *enode.Node { return nil } func (t fakeTable) ReadRandomNodes(buf []*enode.Node) int { return copy(buf, t) } +func (t fakeTable) SeedNodes() []*enode.Node { return nil } // This test checks that dynamic dials are launched from discovery results. func TestDialStateDynDial(t *testing.T) { @@ -703,3 +704,4 @@ func (t *resolveMock) Self() *enode.Node { return new(enode. func (t *resolveMock) Close() {} func (t *resolveMock) LookupRandom() []*enode.Node { return nil } func (t *resolveMock) ReadRandomNodes(buf []*enode.Node) int { return 0 } +func (t *resolveMock) SeedNodes() []*enode.Node { return nil } diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 8247f8b4..97986aae 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -141,6 +141,11 @@ func (tab *Table) Self() *enode.Node { return unwrapNode(tab.self) } +// SeedNodes return all of the seed nodes +func (tab *Table) SeedNodes() []*enode.Node { + return tab.db.QueryAllSeeds() +} + // ReadRandomNodes fills the given slice with random nodes from the table. The results // are guaranteed to be unique for a single invocation, no node will appear twice. func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 442b72bc..e5f71179 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -329,6 +329,30 @@ seek: return nodes } +// QueryAllSeeds retrieves all nodes to be used as potential seed nodes +// for bootstrapping. +func (db *DB) QueryAllSeeds() []*Node { + var ( + nodes []*Node + it = db.lvl.NewIterator(nil, nil) + ) + defer it.Release() +seek: + for it.Next() { + n := nextNode(it) + if n == nil { + break + } + for i := range nodes { + if nodes[i].ID() == n.ID() { + continue seek + } + } + nodes = append(nodes, n) + } + return nodes +} + // reads the next node record from the iterator, skipping over other // database entries. func nextNode(it iterator.Iterator) *Node { diff --git a/p2p/enode/urlv4_test.go b/p2p/enode/urlv4_test.go index 2ddbdb75..8db7b4b7 100644 --- a/p2p/enode/urlv4_test.go +++ b/p2p/enode/urlv4_test.go @@ -43,10 +43,11 @@ var parseNodeTests = []struct { wantError: `invalid node ID (wrong length, want 128 hex chars)`, }, // Complete nodes with IP address. - { - rawurl: "fnode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", - wantError: `invalid port`, - }, + // this test go 1.13 is not work, wantError: `invalid port:foo` + // { + // rawurl: "fnode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", + // wantError: `invalid port`, + // }, { rawurl: "fnode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", wantError: `invalid discport in query`, diff --git a/p2p/protoadaptor/ProtoAdaptor.go b/p2p/protoadaptor/ProtoAdaptor.go index f227d070..28d82bf7 100644 --- a/p2p/protoadaptor/ProtoAdaptor.go +++ b/p2p/protoadaptor/ProtoAdaptor.go @@ -173,16 +173,12 @@ func checkDDOS(m map[int][]int64, e *router.Event) bool { } //m[t][0] time //m[t][1] request per second - if m[t][0] == time.Now().Unix() { - m[t][1]++ - } else { - if m[t][1] > limit { - return true - } + if m[t][0] != time.Now().Unix() { m[t][0] = time.Now().Unix() - m[t][1] = 1 + m[t][1] = 0 } - return false + m[t][1]++ + return m[t][1] > limit } // Protocols . @@ -200,6 +196,7 @@ func (adaptor *ProtoAdaptor) Protocols() []p2p.Protocol { // Stop . func (adaptor *ProtoAdaptor) Stop() { close(adaptor.quit) + router.StationRegister(adaptor.peerMangaer.station) for _, sub := range adaptor.subs { sub.Unsubscribe() } diff --git a/p2p/server.go b/p2p/server.go index 28e06e13..529cabec 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -302,6 +302,10 @@ func (srv *Server) Peers() []*Peer { return ps } +func (srv *Server) SeedNodes() []*enode.Node { + return srv.ntab.SeedNodes() +} + // PeerCount returns the number of connected peers. func (srv *Server) PeerCount() int { var count int @@ -501,6 +505,8 @@ func (srv *Server) DiscoverOnly() error { srv.log = log.New() } + srv.quit = make(chan struct{}) + srv.log.Info("Starting P2P discovery networking", "NetID", srv.magicNetID(), "UsrNetID", srv.NetworkID) // static fields @@ -516,7 +522,6 @@ func (srv *Server) DiscoverOnly() error { if err != nil { return err } - srv.quit = make(chan struct{}) cfg := discover.Config{ TCPPort: 0, MagicNetID: srv.magicNetID(), @@ -531,11 +536,13 @@ func (srv *Server) DiscoverOnly() error { if err != nil { return err } - + srv.ntab = ntab + srv.loopWG.Add(1) go func() { timeout := time.NewTicker(10 * time.Minute) + defer srv.loopWG.Done() defer timeout.Stop() - defer ntab.Close() + defer srv.ntab.Close() for { select { case <-timeout.C: diff --git a/params/chainconfig.go b/params/chainconfig.go index af12c956..5f8b2ae1 100644 --- a/params/chainconfig.go +++ b/params/chainconfig.go @@ -152,7 +152,9 @@ const ( ForkID2 = uint64(2) //ForkID3 dpos config candidateAvailableMinQuantity modified ForkID3 = uint64(3) + //ForkID4 miner pubkey separate + ForkID4 = uint64(4) // NextForkID is the id of next fork - NextForkID uint64 = ForkID3 + NextForkID uint64 = ForkID4 ) diff --git a/processor/error.go b/processor/error.go index 5788cd44..b862bdbd 100644 --- a/processor/error.go +++ b/processor/error.go @@ -55,7 +55,8 @@ var ( errParentBlock = errors.New("parent block not exist") - ErrActionInvalid = errors.New("action field invalid") + ErrActionInvalid = errors.New("action field invalid") + errPayerNotSupport = errors.New("payer not support") ) // GenesisMismatchError is raised when trying to overwrite an existing diff --git a/processor/processor.go b/processor/processor.go index cced30e0..712c3f12 100644 --- a/processor/processor.go +++ b/processor/processor.go @@ -24,6 +24,7 @@ import ( "github.com/fractalplatform/fractal/accountmanager" "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/consensus" + "github.com/fractalplatform/fractal/params" "github.com/fractalplatform/fractal/processor/vm" "github.com/fractalplatform/fractal/state" "github.com/fractalplatform/fractal/types" @@ -100,7 +101,6 @@ func (p *StateProcessor) ApplyTransaction(author *common.Name, gp *common.GasPoo if assetID != tx.GasAssetID() { return nil, 0, fmt.Errorf("only support system asset %d as tx fee", p.bc.Config().SysTokenID) } - gasPrice := tx.GasPrice() //timer for vm exec overtime var t *time.Timer // @@ -125,11 +125,26 @@ func (p *StateProcessor) ApplyTransaction(author *common.Name, gp *common.GasPoo return nil, 0, ErrNonceTooLow } + var gasPayer = action.Sender() + var gasPrice = tx.GasPrice() + if tx.PayerExist() { + if header.CurForkID() >= params.ForkID4 { + gasPayer = action.Payer() + gasPrice = action.PayerGasPrice() + } else { + return nil, 0, errPayerNotSupport + } + } else { + if action.PayerIsExist() { + return nil, 0, errPayerNotSupport + } + } + evmcontext := &EvmContext{ ChainContext: p.bc, EngineContext: p.engine, } - context := NewEVMContext(action.Sender(), action.Recipient(), assetID, tx.GasPrice(), header, evmcontext, author) + context := NewEVMContext(action.Sender(), action.Recipient(), assetID, gasPrice, header, evmcontext, author) vmenv := vm.NewEVM(context, accountDB, statedb, config, cfg) //will abort the vm if overtime @@ -139,7 +154,7 @@ func (p *StateProcessor) ApplyTransaction(author *common.Name, gp *common.GasPoo }) } - _, gas, failed, err, vmerr := ApplyMessage(accountDB, vmenv, action, gp, gasPrice, assetID, config, p.engine) + _, gas, failed, err, vmerr := ApplyMessage(accountDB, vmenv, action, gp, gasPrice, gasPayer, assetID, config, p.engine) if false == cfg.EndTime.IsZero() { //close timer diff --git a/processor/transition.go b/processor/transition.go index 4d518f73..7d89b2f5 100644 --- a/processor/transition.go +++ b/processor/transition.go @@ -44,6 +44,7 @@ type StateTransition struct { gas uint64 initialGas uint64 gasPrice *big.Int + gasPayer common.Name assetID uint64 account *accountmanager.AccountManager evm *vm.EVM @@ -52,7 +53,7 @@ type StateTransition struct { // NewStateTransition initialises and returns a new state transition object. func NewStateTransition(accountDB *accountmanager.AccountManager, evm *vm.EVM, - action *types.Action, gp *common.GasPool, gasPrice *big.Int, assetID uint64, + action *types.Action, gp *common.GasPool, gasPrice *big.Int, gasPayer common.Name, assetID uint64, config *params.ChainConfig, engine EngineContext) *StateTransition { return &StateTransition{ engine: engine, @@ -61,6 +62,7 @@ func NewStateTransition(accountDB *accountmanager.AccountManager, evm *vm.EVM, evm: evm, action: action, gasPrice: gasPrice, + gasPayer: gasPayer, assetID: assetID, account: accountDB, chainConfig: config, @@ -69,9 +71,9 @@ func NewStateTransition(accountDB *accountmanager.AccountManager, evm *vm.EVM, // ApplyMessage computes the new state by applying the given message against the old state within the environment. func ApplyMessage(accountDB *accountmanager.AccountManager, evm *vm.EVM, - action *types.Action, gp *common.GasPool, gasPrice *big.Int, + action *types.Action, gp *common.GasPool, gasPrice *big.Int, gasPayer common.Name, assetID uint64, config *params.ChainConfig, engine EngineContext) ([]byte, uint64, bool, error, error) { - return NewStateTransition(accountDB, evm, action, gp, gasPrice, + return NewStateTransition(accountDB, evm, action, gp, gasPrice, gasPayer, assetID, config, engine).TransitionDb() } @@ -89,7 +91,7 @@ func (st *StateTransition) preCheck() error { func (st *StateTransition) buyGas() error { mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.action.Gas()), st.gasPrice) - balance, err := st.account.GetAccountBalanceByID(st.from, st.assetID, 0) + balance, err := st.account.GetAccountBalanceByID(st.gasPayer, st.assetID, 0) if err != nil { return err } @@ -101,7 +103,7 @@ func (st *StateTransition) buyGas() error { } st.gas += st.action.Gas() st.initialGas = st.action.Gas() - return st.account.TransferAsset(st.from, common.Name(st.chainConfig.FeeName), st.assetID, mgval) + return st.account.TransferAsset(st.gasPayer, common.Name(st.chainConfig.FeeName), st.assetID, mgval) } // TransitionDb will transition the state by applying the current message and @@ -135,10 +137,47 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo ret, st.gas, vmerr = evm.Create(sender, st.action, st.gas) case actionType == types.CallContract: ret, st.gas, vmerr = evm.Call(sender, st.action, st.gas) + case actionType == types.Transfer: + var fromExtra common.Name + if evm.ForkID >= params.ForkID4 { + if asset, err := st.account.GetAssetInfoByID(st.action.AssetID()); err == nil { + assetContract := asset.GetContract() + if len(assetContract) != 0 && assetContract != sender.Name() && assetContract != st.action.Recipient() { + var cantransfer bool + st.gas, cantransfer = evm.CanTransferContractAsset(sender, st.gas, st.action.AssetID(), asset.GetContract()) + if cantransfer { + fromExtra = asset.GetContract() + } + } + } + } + if len(fromExtra) == 0 { + internalLogs, err := st.account.Process(&types.AccountManagerContext{ + Action: st.action, + Number: st.evm.Context.BlockNumber.Uint64(), + CurForkID: st.evm.Context.ForkID, + ChainConfig: st.chainConfig, + }) + vmerr = err + evm.InternalTxs = append(evm.InternalTxs, internalLogs...) + } else { + internalLogs, err := st.account.Process(&types.AccountManagerContext{ + Action: st.action, + Number: st.evm.Context.BlockNumber.Uint64(), + CurForkID: st.evm.Context.ForkID, + ChainConfig: st.chainConfig, + FromAccountExtra: []common.Name{fromExtra}, + }) + vmerr = err + evm.InternalTxs = append(evm.InternalTxs, internalLogs...) + } + case actionType == types.RegCandidate: fallthrough case actionType == types.UpdateCandidate: fallthrough + case actionType == types.UpdateCandidatePubKey: + fallthrough case actionType == types.UnregCandidate: fallthrough case actionType == types.VoteCandidate: @@ -188,29 +227,61 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo if err := st.distributeFee(); err != nil { return ret, st.gasUsed(), true, err, vmerr } + + // action := types.NewAction(types.Transfer, st.from, common.Name(st.chainConfig.FeeName), 0, st.assetID, 0, big.NewInt(0).SetUint64(st.gasUsed()), nil, nil) + // internalAction := &types.InternalAction{Action: action.NewRPCAction(0), ActionType: "addfee", GasUsed: 0, GasLimit: 0, Depth: 0} + // evm.InternalTxs = append(evm.InternalTxs, internalAction) + return ret, st.gasUsed(), vmerr != nil, nil, vmerr } func (st *StateTransition) distributeGas(intrinsicGas uint64) { switch st.action.Type() { case types.Transfer: - assetInfo, _ := st.evm.AccountDB.GetAssetInfoByID(st.action.AssetID()) - assetName := common.Name(assetInfo.GetAssetName()) - assetFounderRatio := st.chainConfig.ChargeCfg.AssetRatio - - key := vm.DistributeKey{ObjectName: assetName, - ObjectType: params.AssetFeeType} - assetGas := int64(st.gasUsed() * assetFounderRatio / 100) - dGas := vm.DistributeGas{ - Value: assetGas, - TypeID: params.AssetFeeType} - st.evm.FounderGasMap[key] = dGas - - key = vm.DistributeKey{ObjectName: st.evm.Coinbase, - ObjectType: params.CoinbaseFeeType} - st.evm.FounderGasMap[key] = vm.DistributeGas{ - Value: int64(st.gasUsed()) - assetGas, - TypeID: params.CoinbaseFeeType} + if st.evm.ForkID >= params.ForkID4 { + if asset, err := st.account.GetAssetInfoByID(st.action.AssetID()); err == nil { + assetContract := asset.GetContract() + if len(assetContract) != 0 && assetContract != st.action.Sender() && assetContract != st.action.Recipient() { + st.distributeToContract(asset.GetContract(), intrinsicGas) + } else { + assetInfo, _ := st.evm.AccountDB.GetAssetInfoByID(st.action.AssetID()) + assetName := common.Name(assetInfo.GetAssetName()) + assetFounderRatio := st.chainConfig.ChargeCfg.AssetRatio + + key := vm.DistributeKey{ObjectName: assetName, + ObjectType: params.AssetFeeType} + assetGas := int64(st.gasUsed() * assetFounderRatio / 100) + dGas := vm.DistributeGas{ + Value: assetGas, + TypeID: params.AssetFeeType} + st.evm.FounderGasMap[key] = dGas + + key = vm.DistributeKey{ObjectName: st.evm.Coinbase, + ObjectType: params.CoinbaseFeeType} + st.evm.FounderGasMap[key] = vm.DistributeGas{ + Value: int64(st.gasUsed()) - assetGas, + TypeID: params.CoinbaseFeeType} + } + } + } else { + assetInfo, _ := st.evm.AccountDB.GetAssetInfoByID(st.action.AssetID()) + assetName := common.Name(assetInfo.GetAssetName()) + assetFounderRatio := st.chainConfig.ChargeCfg.AssetRatio + + key := vm.DistributeKey{ObjectName: assetName, + ObjectType: params.AssetFeeType} + assetGas := int64(st.gasUsed() * assetFounderRatio / 100) + dGas := vm.DistributeGas{ + Value: assetGas, + TypeID: params.AssetFeeType} + st.evm.FounderGasMap[key] = dGas + + key = vm.DistributeKey{ObjectName: st.evm.Coinbase, + ObjectType: params.CoinbaseFeeType} + st.evm.FounderGasMap[key] = vm.DistributeGas{ + Value: int64(st.gasUsed()) - assetGas, + TypeID: params.CoinbaseFeeType} + } case types.CreateContract: fallthrough @@ -243,6 +314,8 @@ func (st *StateTransition) distributeGas(intrinsicGas uint64) { fallthrough case types.UpdateCandidate: fallthrough + case types.UpdateCandidatePubKey: + fallthrough case types.UnregCandidate: fallthrough case types.VoteCandidate: @@ -332,7 +405,7 @@ func (st *StateTransition) distributeFee() error { func (st *StateTransition) refundGas() { remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) - st.account.TransferAsset(common.Name(st.chainConfig.FeeName), st.from, st.assetID, remaining) + st.account.TransferAsset(common.Name(st.chainConfig.FeeName), st.gasPayer, st.assetID, remaining) st.gp.AddGas(st.gas) } diff --git a/processor/validator.go b/processor/validator.go index 1110116e..172bded3 100644 --- a/processor/validator.go +++ b/processor/validator.go @@ -133,9 +133,16 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { return err } - // Header validity is known at this point, check the uncles and transactions - if hash := types.DeriveTxsMerkleRoot(block.Txs); hash != block.TxHash() { - return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, block.TxHash()) + if block.CurForkID() >= params.ForkID4 { + // Header validity is known at this point, check the uncles and transactions + if hash := types.DeriveExtensTxsMerkleRoot(block.Txs); hash != block.TxHash() { + return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, block.TxHash()) + } + } else { + // Header validity is known at this point, check the uncles and transactions + if hash := types.DeriveTxsMerkleRoot(block.Txs); hash != block.TxHash() { + return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, block.TxHash()) + } } return nil } diff --git a/processor/vm/gas_table.go b/processor/vm/gas_table.go index 3a6b54be..f5f79224 100644 --- a/processor/vm/gas_table.go +++ b/processor/vm/gas_table.go @@ -319,6 +319,11 @@ func gasCall(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem gas = gt.Calls transfersValue = stack.Back(2).Sign() != 0 ) + if evm.ForkID >= params.ForkID4 { + if p := PrecompiledContracts[stack.Back(1).Uint64()]; p != nil { + return gas, nil + } + } if transfersValue { gas += gt.CallValueTransferGas } diff --git a/processor/vm/instructions.go b/processor/vm/instructions.go index ca9045cf..f2afe42d 100644 --- a/processor/vm/instructions.go +++ b/processor/vm/instructions.go @@ -522,6 +522,7 @@ func opSnapBalance(pc *uint64, evm *EVM, contract *Contract, memory *Memory, sta evm.interpreter.intPool.put(time, assetId) return nil, nil } + func opBalanceex(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { assetId := stack.pop() slot := stack.peek() @@ -1598,9 +1599,12 @@ func execWithdrawFee(evm *EVM, contract *Contract, withdrawTo common.Name, objec return errEnc } - action := types.NewAction(types.Transfer, common.Name(evm.chainConfig.FeeName), withdrawInfo.Founder, 0, 0, 0, big.NewInt(0), paload, nil) - internalAction := &types.InternalAction{Action: action.NewRPCAction(0), ActionType: "transfer", GasUsed: 0, GasLimit: contract.Gas, Depth: uint64(evm.depth)} - evm.InternalTxs = append(evm.InternalTxs, internalAction) + for _, assetInfo := range withdrawInfo.AssetInfo { + action := types.NewAction(types.Transfer, common.Name(evm.chainConfig.FeeName), withdrawInfo.Founder, 0, assetInfo.AssetID, 0, assetInfo.Amount, paload, nil) + internalAction := &types.InternalAction{Action: action.NewRPCAction(0), ActionType: "transfer", GasUsed: 0, GasLimit: contract.Gas, Depth: uint64(evm.depth)} + evm.InternalTxs = append(evm.InternalTxs, internalAction) + } + } return err } @@ -1628,7 +1632,21 @@ func opCallEx(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *S return nil, nil } - err = evm.AccountDB.TransferAsset(action.Sender(), action.Recipient(), action.AssetID(), action.Value()) + var fromExtra common.Name + if evm.ForkID >= params.ForkID4 { + if asset, err := evm.AccountDB.GetAssetInfoByID(action.AssetID()); err == nil { + assetContract := asset.GetContract() + if len(assetContract) != 0 && assetContract != action.Sender() && assetContract != action.Recipient() { + var cantransfer bool + contract.Gas, cantransfer = evm.CanTransferContractAsset(contract, contract.Gas, action.AssetID(), assetContract) + if cantransfer { + fromExtra = assetContract + } + } + } + } + + err = evm.AccountDB.TransferAsset(action.Sender(), action.Recipient(), action.AssetID(), action.Value(), fromExtra) //distribute gas var assetName common.Name assetFounder, _ := evm.AccountDB.GetAssetFounder(action.AssetID()) //get asset founder name diff --git a/processor/vm/vm.go b/processor/vm/vm.go index 68741723..601905a7 100644 --- a/processor/vm/vm.go +++ b/processor/vm/vm.go @@ -30,6 +30,8 @@ import ( "github.com/fractalplatform/fractal/types" ) +var contractAssetTransferable = common.Hex2Bytes("92ff0d31") + type ( // GetHashFunc returns the nth block hash in the blockchain and is used by the BLOCKHASH EVM op code. GetHashFunc func(uint64) common.Hash @@ -312,7 +314,21 @@ func (evm *EVM) Call(caller ContractRef, action *types.Action, gas uint64) (ret } } - if err := evm.AccountDB.TransferAsset(action.Sender(), action.Recipient(), action.AssetID(), action.Value()); err != nil { + var fromExtra common.Name + if evm.ForkID >= params.ForkID4 { + if asset, err := evm.AccountDB.GetAssetInfoByID(action.AssetID()); err == nil { + assetContract := asset.GetContract() + if len(assetContract) != 0 && assetContract != caller.Name() && assetContract != action.Recipient() { + var cantransfer bool + gas, cantransfer = evm.CanTransferContractAsset(caller, gas, action.AssetID(), assetContract) + if cantransfer { + fromExtra = assetContract + } + } + } + } + + if err := evm.AccountDB.TransferAsset(action.Sender(), action.Recipient(), action.AssetID(), action.Value(), fromExtra); err != nil { return nil, gas, err } @@ -564,9 +580,17 @@ func (evm *EVM) Create(caller ContractRef, action *types.Action, gas uint64) (re snapshot := evm.StateDB.Snapshot() if b, err := evm.AccountDB.AccountHaveCode(contractName); err != nil { - return nil, 0, err + if evm.ForkID >= params.ForkID4 { + return nil, gas, err + } else { + return nil, 0, err + } } else if b { - return nil, 0, ErrContractCodeCollision + if evm.ForkID >= params.ForkID4 { + return nil, gas, ErrContractCodeCollision + } else { + return nil, 0, ErrContractCodeCollision + } } if err := evm.AccountDB.TransferAsset(action.Sender(), action.Recipient(), evm.AssetID, action.Value()); err != nil { @@ -629,6 +653,57 @@ func (evm *EVM) Create(caller ContractRef, action *types.Action, gas uint64) (re return ret, contract.Gas, err } +func (evm *EVM) CanTransferContractAsset(caller ContractRef, gas uint64, assetID uint64, assetContract common.Name) (uint64, bool) { + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return gas, false + } + var ( + to = AccountRef(assetContract) + snapshot = evm.StateDB.Snapshot() + ) + + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, to, big.NewInt(0), gas, assetID) + acct, err := evm.AccountDB.GetAccountByName(assetContract) + if err != nil { + return 0, false + } + if acct == nil { + return 0, false + } + codeHash, err := acct.GetCodeHash() + if err != nil { + return 0, false + } + code, _ := acct.GetCode() + contract.SetCallCode(&assetContract, codeHash, code) + + ret, err := run(evm, contract, contractAssetTransferable) + runGas := gas - contract.Gas + + evm.distributeContractGas(runGas, assetContract, caller.Name()) + + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + actualUsedGas := gas - contract.Gas + evm.distributeGasByScale(actualUsedGas, runGas) + + if new(big.Int).SetBytes(ret).Cmp(big.NewInt(0)) > 0 && err == nil { + return contract.Gas, true + } else { + return contract.Gas, false + } +} + // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } diff --git a/rpc/client.go b/rpc/client.go index 14f01e7b..41fc52f0 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -283,6 +283,36 @@ func (c *Client) Call(result interface{}, method string, args ...interface{}) er return c.CallContext(ctx, result, method, args...) } +func (c *Client) CallRaw(method string, args ...interface{}) (json.RawMessage, error) { + ctx := context.Background() + msg, err := c.newMessage(method, args...) + if err != nil { + return nil, err + } + op := &requestOp{ids: []json.RawMessage{msg.ID}, resp: make(chan *jsonrpcMessage, 1)} + + if c.isHTTP { + err = c.sendHTTP(ctx, op, msg) + } else { + err = c.send(ctx, op, msg) + } + if err != nil { + return nil, err + } + + // dispatch has accepted the request and will close the channel it when it quits. + switch resp, err := op.wait(ctx); { + case err != nil: + return nil, err + case resp.Error != nil: + return nil, resp.Error + case len(resp.Result) == 0: + return nil, ErrNoResult + default: + return resp.Result, nil + } +} + // CallContext performs a JSON-RPC call with the given arguments. If the context is // canceled before the call has successfully returned, CallContext returns immediately. // diff --git a/rpcapi/backend.go b/rpcapi/backend.go index 0d926eb0..5c975f62 100644 --- a/rpcapi/backend.go +++ b/rpcapi/backend.go @@ -81,6 +81,7 @@ type Backend interface { RemovePeer(url string) error AddTrustedPeer(url string) error RemoveTrustedPeer(url string) error + SeedNodes() []string PeerCount() int Peers() []string BadNodesCount() int diff --git a/rpcapi/blockchain.go b/rpcapi/blockchain.go index f0b00e7b..48b85faa 100644 --- a/rpcapi/blockchain.go +++ b/rpcapi/blockchain.go @@ -80,6 +80,21 @@ func (s *PublicBlockChainAPI) rpcOutputBlock(chainID *big.Int, b *types.Block, i return fields } +func (s *PublicBlockChainAPI) GetBlockByNumberWithPayer(ctx context.Context, blockNr rpc.BlockNumber, fullTx bool) map[string]interface{} { + block := s.b.BlockByNumber(ctx, blockNr) + if block != nil { + response := s.rpcOutputBlockWithPayer(s.b.ChainConfig().ChainID, block, true, fullTx) + return response + } + return nil +} + +func (s *PublicBlockChainAPI) rpcOutputBlockWithPayer(chainID *big.Int, b *types.Block, inclTx bool, fullTx bool) map[string]interface{} { + fields := RPCMarshalBlockWithPayer(chainID, b, inclTx, fullTx) + fields["totalDifficulty"] = s.b.GetTd(b.Hash()) + return fields +} + // GetTransactionByHash returns the transaction for the given hash func (s *PublicBlockChainAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *types.RPCTransaction { // Try to return an already finalized transaction @@ -126,6 +141,23 @@ func (s *PublicBlockChainAPI) GetTransactionReceipt(ctx context.Context, hash co return receipt.NewRPCReceipt(blockHash, blockNumber, index, tx), nil } +func (s *PublicBlockChainAPI) GetTransactionReceiptWithPayer(ctx context.Context, hash common.Hash) (*types.RPCReceiptWithPayer, error) { + tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash) + if tx == nil { + return nil, nil + } + + receipts, err := s.b.GetReceipts(ctx, blockHash) + if err != nil { + return nil, err + } + if len(receipts) <= int(index) { + return nil, nil + } + receipt := receipts[index] + return receipt.NewRPCReceiptWithPayer(blockHash, blockNumber, index, tx), nil +} + func (s *PublicBlockChainAPI) GetBlockAndResultByNumber(ctx context.Context, blockNr rpc.BlockNumber) *types.BlockAndResult { r := s.b.GetBlockDetailLog(ctx, blockNr) if r == nil { @@ -136,6 +168,16 @@ func (s *PublicBlockChainAPI) GetBlockAndResultByNumber(ctx context.Context, blo return r } +func (s *PublicBlockChainAPI) GetBlockAndResultByNumberWithPayer(ctx context.Context, blockNr rpc.BlockNumber) *types.BlockAndResult { + r := s.b.GetBlockDetailLog(ctx, blockNr) + if r == nil { + return nil + } + block := s.GetBlockByNumberWithPayer(ctx, blockNr, true) + r.Block = block + return r +} + // checkRangeInputArgs checks the input arguments of // GetTxsByAccount,GetTxsByBloom,GetInternalTxByAccount,GetInternalTxByBloom func (s *PublicBlockChainAPI) checkRangeInputArgs(blockNr, lookbackNum uint64) error { @@ -306,7 +348,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr // and apply the message. gp := new(common.GasPool).AddGas(math.MaxUint64) action := types.NewAction(args.ActionType, args.From, args.To, 0, assetID, gas, value, args.Data, args.Remark) - res, gas, failed, err, _ := processor.ApplyMessage(account, evm, action, gp, gasPrice, assetID, s.b.ChainConfig(), s.b.Engine()) + res, gas, failed, err, _ := processor.ApplyMessage(account, evm, action, gp, gasPrice, action.Sender(), assetID, s.b.ChainConfig(), s.b.Engine()) if err := vmError(); err != nil { return nil, 0, false, err } diff --git a/rpcapi/p2p.go b/rpcapi/p2p.go index a436b349..a2458b0d 100644 --- a/rpcapi/p2p.go +++ b/rpcapi/p2p.go @@ -111,6 +111,11 @@ func (api *PrivateP2pAPI) RemoveTrustedPeer(url string) (bool, error) { return true, nil } +// SeedNodes returns all seed nodes. +func (api *PrivateP2pAPI) SeedNodes() []string { + return api.b.SeedNodes() +} + // PeerCount return number of connected peers func (api *PrivateP2pAPI) PeerCount() int { return api.b.PeerCount() diff --git a/rpcapi/txpool.go b/rpcapi/txpool.go index 40b80333..4f1d1598 100644 --- a/rpcapi/txpool.go +++ b/rpcapi/txpool.go @@ -79,7 +79,7 @@ func (s *PrivateTxPoolAPI) Content(fullTx bool) interface{} { return content } -// PendingTransactions returns the pending transactions that are in the transaction pool +// PendingTransactions returns the pending transactions that are in the transaction pool. func (s *PrivateTxPoolAPI) PendingTransactions(fullTx bool) (interface{}, error) { pending, err := s.b.TxPool().Pending() if err != nil { @@ -106,8 +106,8 @@ func (s *PrivateTxPoolAPI) PendingTransactions(fullTx bool) (interface{}, error) return txsHashes, nil } -// GetPoolTransactions txpool returns the transaction for the given hash -func (s *PrivateTxPoolAPI) GetPoolTransactions(hashes []common.Hash) []*types.RPCTransaction { +// GetTransactions txpool returns the transaction by the given hash. +func (s *PrivateTxPoolAPI) GetTransactions(hashes []common.Hash) []*types.RPCTransaction { var txs []*types.RPCTransaction for _, hash := range hashes { if tx := s.b.TxPool().Get(hash); tx != nil { @@ -117,6 +117,36 @@ func (s *PrivateTxPoolAPI) GetPoolTransactions(hashes []common.Hash) []*types.RP return txs } +// GetTransactionsByAccount txpool returns the transaction by the given account name. +func (s *PrivateTxPoolAPI) GetTransactionsByAccount(name common.Name, fullTx bool) interface{} { + content := map[string]map[string]interface{}{ + "pending": make(map[string]interface{}), + "queued": make(map[string]interface{}), + } + + txsFunc := func(name common.Name, m map[common.Name][]*types.Transaction, fullTx bool) map[string]interface{} { + dump := make(map[string]interface{}) + txs, ok := m[name] + if ok { + for _, tx := range txs { + if fullTx { + dump[fmt.Sprintf("%d", tx.GetActions()[0].Nonce())] = tx.NewRPCTransaction(common.Hash{}, 0, 0) + } else { + dump[fmt.Sprintf("%d", tx.GetActions()[0].Nonce())] = tx.Hash() + } + } + } + return dump + } + + pending, queue := s.b.TxPool().Content() + + content["pending"] = txsFunc(name, pending, fullTx) + content["queued"] = txsFunc(name, queue, fullTx) + + return content +} + // SetGasPrice set txpool gas price func (s *PrivateTxPoolAPI) SetGasPrice(gasprice *big.Int) bool { return s.b.SetGasPrice(gasprice) diff --git a/rpcapi/utils.go b/rpcapi/utils.go index dd8f2c4f..6c240dbd 100644 --- a/rpcapi/utils.go +++ b/rpcapi/utils.go @@ -78,3 +78,44 @@ func RPCMarshalBlock(chainID *big.Int, b *types.Block, inclTx bool, fullTx bool) return fields } + +func RPCMarshalBlockWithPayer(chainID *big.Int, b *types.Block, inclTx bool, fullTx bool) map[string]interface{} { + head := b.Header() // copies the header once + fields := map[string]interface{}{ + "number": head.Number, + "hash": b.Hash(), + "proposedIrreversible": head.ProposedIrreversible, + "parentHash": head.ParentHash, + "logsBloom": head.Bloom, + "stateRoot": head.Root, + "miner": head.Coinbase, + "difficulty": head.Difficulty, + "extraData": hexutil.Bytes(head.Extra), + "size": b.Size(), + "gasLimit": head.GasLimit, + "gasUsed": head.GasUsed, + "timestamp": head.Time, + "transactionsRoot": head.TxsRoot, + "receiptsRoot": head.ReceiptsRoot, + "forkID": head.ForkID, + } + + if inclTx { + formatTx := func(tx *types.Transaction, index uint64) interface{} { + return tx.Hash() + } + if fullTx { + formatTx = func(tx *types.Transaction, index uint64) interface{} { + return tx.NewRPCTransactionWithPayer(b.Hash(), b.NumberU64(), index) + } + } + txs := b.Transactions() + transactions := make([]interface{}, len(txs)) + for i, tx := range txs { + transactions[i] = formatTx(tx, uint64(i)) + } + fields["transactions"] = transactions + } + + return fields +} diff --git a/scripts/commit_hash.sh b/scripts/commit_hash.sh index dd3511dd..51662737 100755 --- a/scripts/commit_hash.sh +++ b/scripts/commit_hash.sh @@ -19,6 +19,9 @@ # Gets the git commit hash of the working dir and adds an additional hash of any tracked modified files commit=$(git describe --tags) dirty=$(git ls-files -m) +branch=$(git branch | grep '*' | cut -d ' ' -f 2) + +commit="$commit+branch.$branch" if [[ -n ${dirty} ]]; then commit="$commit+dirty.$(echo ${dirty} | git hash-object --stdin | head -c8)" fi diff --git a/sdk/account_test.go b/sdk/account_test.go index f0e88f07..fad9fbc8 100644 --- a/sdk/account_test.go +++ b/sdk/account_test.go @@ -193,7 +193,7 @@ func TestRegCandidate(t *testing.T) { // RegCandidate act := NewAccount(api, common.StrToName(name), priv, chainCfg.SysTokenID, math.MaxUint64, true, chainCfg.ChainID) hash, err := act.RegCandidate(common.StrToName(chainCfg.DposName), new(big.Int).Mul(new(big.Int).Div(val, big.NewInt(4)), decimals), chainCfg.SysTokenID, gas, &dpos.RegisterCandidate{ - URL: fmt.Sprintf("www.%s.com", name), + Info: fmt.Sprintf("www.%s.com", name), }) So(err, ShouldBeNil) So(hash, ShouldNotBeNil) @@ -204,7 +204,7 @@ func TestUpdateCandidate(t *testing.T) { // UpdateCandidate act := NewAccount(api, common.StrToName(name), priv, chainCfg.SysTokenID, math.MaxUint64, true, chainCfg.ChainID) hash, err := act.UpdateCandidate(common.StrToName(chainCfg.DposName), new(big.Int).Mul(new(big.Int).Div(val, big.NewInt(4)), decimals), chainCfg.SysTokenID, gas, &dpos.UpdateCandidate{ - URL: fmt.Sprintf("www.%s.com", name), + Info: fmt.Sprintf("www.%s.com", name), }) So(err, ShouldBeNil) So(hash, ShouldNotBeNil) diff --git a/sdk/test/dpos/createandregcandidate.json b/sdk/test/dpos/createandregcandidate.json index 65c3d818..51d7ec01 100644 --- a/sdk/test/dpos/createandregcandidate.json +++ b/sdk/test/dpos/createandregcandidate.json @@ -22,7 +22,7 @@ "gas": 30000000, "value": 20000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true, @@ -34,7 +34,7 @@ "gas": 30000000, "value": 5000000000000000000000, "payload": { - "URL": "www.dpos0001.com" + "Info": "www.dpos0001.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -64,7 +64,7 @@ "gas": 30000000, "value": 20000000000000000000000, "payload": { - "URL": "www.xxxxx.com" + "Info": "www.xxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true, @@ -76,7 +76,7 @@ "gas": 30000000, "value": 5000000000000000000000, "payload": { - "URL": "www.dpos0002.com" + "Info": "www.dpos0002.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -106,7 +106,7 @@ "gas": 30000000, "value": 20000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true, @@ -118,7 +118,7 @@ "gas": 30000000, "value": 5000000000000000000000, "payload": { - "URL": "www.dpos0003.com" + "Info": "www.dpos0003.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -148,7 +148,7 @@ "gas": 30000000, "value": 20000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true, @@ -160,7 +160,7 @@ "gas": 30000000, "value": 5000000000000000000000, "payload": { - "URL": "www.dpos0004.com" + "Info": "www.dpos0004.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -190,7 +190,7 @@ "gas": 30000000, "value": 20000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true, @@ -202,7 +202,7 @@ "gas": 30000000, "value": 5000000000000000000000, "payload": { - "URL": "www.dpos0005.com" + "Info": "www.dpos0005.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -232,7 +232,7 @@ "gas": 30000000, "value": 20000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true, @@ -244,7 +244,7 @@ "gas": 30000000, "value": 5000000000000000000000, "payload": { - "URL": "www.dpos0006.com" + "Info": "www.dpos0006.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/dpos/dposstartlowc.json b/sdk/test/dpos/dposstartlowc.json index 18d9dafc..0d98b63b 100644 --- a/sdk/test/dpos/dposstartlowc.json +++ b/sdk/test/dpos/dposstartlowc.json @@ -22,7 +22,7 @@ "gas": 30000000, "value": 500000000000000000000000, "payload": { - "URL": "www.dpos0001.com" + "Info": "www.dpos0001.com" }, "succeed": true }] @@ -51,7 +51,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0002.com" + "Info": "www.dpos0002.com" }, "succeed": true }] @@ -80,7 +80,7 @@ "gas": 30000000, "value": 3000000000000000000000000, "payload": { - "URL": "www.dpos0006.com" + "Info": "www.dpos0006.com" }, "succeed": true, "childs": [{ @@ -91,7 +91,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0006.com" + "Info": "www.dpos0006.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -132,7 +132,7 @@ "gas": 30000000, "value": 4000000000000000000000000, "payload": { - "URL": "www.dpos0007.com" + "Info": "www.dpos0007.com" }, "succeed": true, "childs": [{ @@ -143,7 +143,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0007.com" + "Info": "www.dpos0007.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -184,7 +184,7 @@ "gas": 30000000, "value": 3000000000000000000000000, "payload": { - "URL": "www.dpos0008.com" + "Info": "www.dpos0008.com" }, "succeed": true, "childs": [{ @@ -195,7 +195,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0008.com" + "Info": "www.dpos0008.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -239,7 +239,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0007.com" + "Info": "www.dpos0007.com" }, "succeed": true, "childs": [{ @@ -250,7 +250,7 @@ "gas": 30000000, "value": 3000000000000000000000000, "payload": { - "URL": "www.dpos0009.com" + "Info": "www.dpos0009.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/dpos/dposstartlowq.json b/sdk/test/dpos/dposstartlowq.json index 6978c912..d7ba7659 100644 --- a/sdk/test/dpos/dposstartlowq.json +++ b/sdk/test/dpos/dposstartlowq.json @@ -22,7 +22,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.dpos0001.com" + "Info": "www.dpos0001.com" }, "succeed": true }] @@ -51,7 +51,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.dpos0002.com" + "Info": "www.dpos0002.com" }, "succeed": true }] @@ -80,7 +80,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.dpos0003.com" + "Info": "www.dpos0003.com" }, "succeed": true }] @@ -109,7 +109,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.dpos0004.com" + "Info": "www.dpos0004.com" }, "succeed": true }] @@ -138,7 +138,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.dpos0005.com" + "Info": "www.dpos0005.com" }, "succeed": true }] @@ -167,7 +167,7 @@ "gas": 30000000, "value": 3000000000000000000000000, "payload": { - "URL": "www.dpos0006.com" + "Info": "www.dpos0006.com" }, "succeed": true, "childs": [{ @@ -178,7 +178,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0006.com" + "Info": "www.dpos0006.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -219,7 +219,7 @@ "gas": 30000000, "value": 4000000000000000000000000, "payload": { - "URL": "www.dpos0007.com" + "Info": "www.dpos0007.com" }, "succeed": true, "childs": [{ @@ -230,7 +230,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0007.com" + "Info": "www.dpos0007.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -271,7 +271,7 @@ "gas": 30000000, "value": 3000000000000000000000000, "payload": { - "URL": "www.dpos0008.com" + "Info": "www.dpos0008.com" }, "succeed": true, "childs": [{ @@ -282,7 +282,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0008.com" + "Info": "www.dpos0008.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -326,7 +326,7 @@ "gas": 30000000, "value": 4000000000000000000000000, "payload": { - "URL": "www.dpos0007.com" + "Info": "www.dpos0007.com" }, "succeed": true, "childs": [{ @@ -337,7 +337,7 @@ "gas": 30000000, "value": 1000000000000000000000000, "payload": { - "URL": "www.dpos0009.com" + "Info": "www.dpos0009.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/dpos/dposstartonly.json b/sdk/test/dpos/dposstartonly.json index 55275ddf..ecce97a6 100644 --- a/sdk/test/dpos/dposstartonly.json +++ b/sdk/test/dpos/dposstartonly.json @@ -22,7 +22,7 @@ "gas": 30000000, "value": 20000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "succeed": true, "childs": [{ @@ -33,7 +33,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.dpos0001.com" + "Info": "www.dpos0001.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -64,7 +64,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "succeed": true, "childs": [{ @@ -75,7 +75,7 @@ "gas": 30000000, "value": 20000000000000000000000, "payload": { - "URL": "www.dpos0002.com" + "Info": "www.dpos0002.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -105,7 +105,7 @@ "gas": 30000000, "value": 30000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true, @@ -117,7 +117,7 @@ "gas": 30000000, "value": 5000000000000000000000, "payload": { - "URL": "www.dpos0003.com" + "Info": "www.dpos0003.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -130,7 +130,7 @@ "gas": 30000000, "value": 5000000000000000000000, "payload": { - "URL": "www.dpos0003.com" + "Info": "www.dpos0003.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/dpos/regcandidate.json b/sdk/test/dpos/regcandidate.json index 2956d5a6..ef18ab9d 100644 --- a/sdk/test/dpos/regcandidate.json +++ b/sdk/test/dpos/regcandidate.json @@ -6,7 +6,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.dpos0001.com" + "Info": "www.dpos0001.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/dpos/updatecandidate.json b/sdk/test/dpos/updatecandidate.json index ca30e83f..3d2348e9 100644 --- a/sdk/test/dpos/updatecandidate.json +++ b/sdk/test/dpos/updatecandidate.json @@ -6,7 +6,7 @@ "gas": 30000000, "value": 0, "payload": { - "URL": "www.dpos0001.com" + "Info": "www.dpos0001.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/sample.go b/sdk/test/sample.go index d4b7015a..b6e94548 100644 --- a/sdk/test/sample.go +++ b/sdk/test/sample.go @@ -214,7 +214,7 @@ func sampleRegCandidate() *TTX { AssetID: chainCfg.SysTokenID, Value: big.NewInt(0), Payload: &dpos.RegisterCandidate{ - URL: fmt.Sprintf("www.xxxxxx.com"), + Info: fmt.Sprintf("www.xxxxxx.com"), }, Succeed: false, Childs: []*TTX{}, @@ -232,7 +232,7 @@ func sampleUpdateCandidate() *TTX { AssetID: chainCfg.SysTokenID, Value: big.NewInt(0), Payload: &dpos.UpdateCandidate{ - URL: fmt.Sprintf("www.xxxxxx.com"), + Info: fmt.Sprintf("www.xxxxxx.com"), }, Succeed: true, Childs: []*TTX{}, diff --git a/sdk/test/sample.json b/sdk/test/sample.json index a73e454d..9d5a28d5 100644 --- a/sdk/test/sample.json +++ b/sdk/test/sample.json @@ -1 +1 @@ -[{"type":"createaccount","from":"fractal.founder","to":"fractal.account","gas":30000000,"value":100000000000000000000,"payload":{"accountName":"sampleact","founder":"sampleact","publicKey":"0x047db227d7094ce215c3a0f57e1bcc732551fe351f94249471934567e0f5dc1bf795962b8cccb87a2eb56b29fbe37d614e2f4c3c45b789ae4f1f51f4cb21972ffd","description":"sample account"},"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"regcandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"payload":{"URL":"www.xxxxxx.com"},"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"updatecandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"payload":{"URL":"www.xxxxxx.com"},"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"unregcandidate","from":"sampleact","to":"fractal.dpos","gas":30000000,"value":0,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"refundcandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"votecandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"payload":{"Candidate":"fractal.founder","Stake":1000000000000000000000},"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"kickedcandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"payload":{"Candidates":["candidate"]},"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"exittakeover","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"}] \ No newline at end of file +[{"type":"createaccount","from":"fractal.founder","to":"fractal.account","gas":30000000,"value":100000000000000000000,"payload":{"accountName":"sampleact","founder":"sampleact","publicKey":"0x047db227d7094ce215c3a0f57e1bcc732551fe351f94249471934567e0f5dc1bf795962b8cccb87a2eb56b29fbe37d614e2f4c3c45b789ae4f1f51f4cb21972ffd","description":"sample account"},"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"regcandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"payload":{"Info":"www.xxxxxx.com"},"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"updatecandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"payload":{"Info":"www.xxxxxx.com"},"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"unregcandidate","from":"sampleact","to":"fractal.dpos","gas":30000000,"value":0,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"refundcandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"votecandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"payload":{"Candidate":"fractal.founder","Stake":1000000000000000000000},"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"kickedcandidate","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"payload":{"Candidates":["candidate"]},"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"},{"type":"exittakeover","from":"fractal.founder","to":"fractal.dpos","gas":30000000,"value":0,"succeed":true,"priv":"289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032"}] \ No newline at end of file diff --git a/sdk/test/testcase/dpos/0001regcandidate.json b/sdk/test/testcase/dpos/0001regcandidate.json index aca4f28f..987ae5e9 100644 --- a/sdk/test/testcase/dpos/0001regcandidate.json +++ b/sdk/test/testcase/dpos/0001regcandidate.json @@ -22,7 +22,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -36,7 +36,7 @@ "gas": 30000000, "value": 11111000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -50,7 +50,7 @@ "gas": 30000000, "value": 0, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -64,7 +64,7 @@ "gas": 30000000, "value": 1000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -78,25 +78,25 @@ "gas": 30000000, "value": 2000000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, "contain": "insufficient funds for value" }, { - "comment": "candidate0001 regcandidate & transfer 10000 ft, wrong url", + "comment": "candidate0001 regcandidate & transfer 10000 ft, wrong Info", "type": "regcandidate", "from": "candidate0001", "to": "fractal.dpos", "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.com" + "Info": "www.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, - "contain": "invalid url" + "contain": "invalid Info" }, { "comment": "candidate0001 regcandidate & transfer 10000 ft", @@ -106,7 +106,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -119,7 +119,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, diff --git a/sdk/test/testcase/dpos/0002updatecandidate.json b/sdk/test/testcase/dpos/0002updatecandidate.json index ed57aef7..41816857 100644 --- a/sdk/test/testcase/dpos/0002updatecandidate.json +++ b/sdk/test/testcase/dpos/0002updatecandidate.json @@ -22,7 +22,7 @@ "gas": 30000000, "value": 0, "payload": { - "URL": "www.candidate0002.com" + "Info": "www.candidate0002.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -36,7 +36,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -50,25 +50,25 @@ "gas": 30000000, "value": 0, "payload": { - "URL": "www.candidate0002.com" + "Info": "www.candidate0002.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, "contain": "fractal.dpos" }, { - "comment": "candidate0002 updatecandidate & transfer 0 ft, wrong url", + "comment": "candidate0002 updatecandidate & transfer 0 ft, wrong Info", "type": "updatecandidate", "from": "candidate0002", "to": "fractal.dpos", "gas": 30000000, "value": 0, "payload": { - "URL": "www.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.com" + "Info": "www.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.comwww.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, - "contain": "invalid url" + "contain": "invalid Info" }, { "comment": "candidate0002 updatecandidate & transfer 1001 ft, non divisibility", "type": "updatecandidate", @@ -77,7 +77,7 @@ "gas": 30000000, "value": 1001000000000000000000, "payload": { - "URL": "www.candidate0002.com" + "Info": "www.candidate0002.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -90,7 +90,7 @@ "gas": 30000000, "value": 1100000000000000000000000, "payload": { - "URL": "www.candidate0002.com" + "Info": "www.candidate0002.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -104,7 +104,7 @@ "gas": 30000000, "value": 0, "payload": { - "URL": "www.candidate0002.com" + "Info": "www.candidate0002.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -117,7 +117,7 @@ "gas": 30000000, "value": 1000000000000000000000, "payload": { - "URL": "www.candidate0002.com" + "Info": "www.candidate0002.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/testcase/dpos/0003unregcandidate.json b/sdk/test/testcase/dpos/0003unregcandidate.json index c71c1613..065eb820 100644 --- a/sdk/test/testcase/dpos/0003unregcandidate.json +++ b/sdk/test/testcase/dpos/0003unregcandidate.json @@ -33,7 +33,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -78,7 +78,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -92,7 +92,7 @@ "gas": 30000000, "value": 0, "payload": { - "URL": "www.candidate0003.com" + "Info": "www.candidate0003.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, diff --git a/sdk/test/testcase/dpos/0004votecandidate.json b/sdk/test/testcase/dpos/0004votecandidate.json index 1722ba70..912f489f 100644 --- a/sdk/test/testcase/dpos/0004votecandidate.json +++ b/sdk/test/testcase/dpos/0004votecandidate.json @@ -126,7 +126,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.candidate0004.com" + "Info": "www.candidate0004.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/testcase/dpos/0005refundcandidate.json b/sdk/test/testcase/dpos/0005refundcandidate.json index 0059e3f9..04a6dcab 100644 --- a/sdk/test/testcase/dpos/0005refundcandidate.json +++ b/sdk/test/testcase/dpos/0005refundcandidate.json @@ -33,7 +33,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true diff --git a/sdk/test/testcase/dpos/0006kickedcandidate.json b/sdk/test/testcase/dpos/0006kickedcandidate.json index e0be11fe..c365add9 100644 --- a/sdk/test/testcase/dpos/0006kickedcandidate.json +++ b/sdk/test/testcase/dpos/0006kickedcandidate.json @@ -22,7 +22,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": true @@ -90,7 +90,7 @@ "gas": 30000000, "value": 10000000000000000000000, "payload": { - "URL": "www.xxxxxx.com" + "Info": "www.xxxxxx.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, @@ -104,7 +104,7 @@ "gas": 30000000, "value": 0, "payload": { - "URL": "www.candidate0006.com" + "Info": "www.candidate0006.com" }, "priv": "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032", "succeed": false, diff --git a/test/common/dpos.go b/test/common/dpos.go index fb3b4131..2937d2bb 100644 --- a/test/common/dpos.go +++ b/test/common/dpos.go @@ -100,8 +100,8 @@ func (acc *Account) Transfer(to common.Name, value *big.Int, id uint64, gas uint } // RegCandidate -func (acc *Account) RegCandidate(to common.Name, value *big.Int, id uint64, gas uint64, url string, state *big.Int) []byte { - arg := &args.RegisterCandidate{URL: url} +func (acc *Account) RegCandidate(to common.Name, value *big.Int, id uint64, gas uint64, info string, state *big.Int) []byte { + arg := &args.RegisterCandidate{Info: info} payload, err := rlp.EncodeToBytes(arg) if err != nil { panic(err) @@ -127,8 +127,8 @@ func (acc *Account) RegCandidate(to common.Name, value *big.Int, id uint64, gas } // UpdateCandidate -func (acc *Account) UpdateCandidate(to common.Name, value *big.Int, id uint64, gas uint64, url string, state *big.Int) []byte { - arg := &args.UpdateCandidate{URL: url} +func (acc *Account) UpdateCandidate(to common.Name, value *big.Int, id uint64, gas uint64, info string, state *big.Int) []byte { + arg := &args.UpdateCandidate{Info: info} payload, err := rlp.EncodeToBytes(arg) if err != nil { panic(err) diff --git a/test/multisig/transaction_contract.go b/test/multisig/transaction_contract.go index 97d31fd6..5d094171 100644 --- a/test/multisig/transaction_contract.go +++ b/test/multisig/transaction_contract.go @@ -36,9 +36,10 @@ var ( privateKey, _ = crypto.HexToECDSA("289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032") from = common.Name("fractal.founder") to = common.Name("fractal.account") - aca = common.Name("accounta") - acb = common.Name("accountb") - acc = common.Name("accountc") + aca = common.Name("fcoinaccounta") + acb = common.Name("fcoinaccountb") + acc = common.Name("fcoinaccountc") + acd = common.Name("fcoinaccountd") a_author_0_priv *ecdsa.PrivateKey a_author_2_priv *ecdsa.PrivateKey @@ -48,17 +49,22 @@ var ( c_author_0_priv *ecdsa.PrivateKey c_author_1_priv *ecdsa.PrivateKey c_author_2_priv *ecdsa.PrivateKey + d_author_0_priv *ecdsa.PrivateKey newPrivateKey_a *ecdsa.PrivateKey newPrivateKey_b *ecdsa.PrivateKey newPrivateKey_c *ecdsa.PrivateKey - pubKey_a common.PubKey - pubKey_b common.PubKey - pubKey_c common.PubKey + newPrivateKey_d *ecdsa.PrivateKey + + pubKey_a common.PubKey + pubKey_b common.PubKey + pubKey_c common.PubKey + pubKey_d common.PubKey aNonce = uint64(0) bNonce = uint64(0) cNonce = uint64(0) + dNonce = uint64(0) assetID = uint64(0) nonce = uint64(0) @@ -83,12 +89,18 @@ func generateAccount() { c_author_0_priv = newPrivateKey_c fmt.Println("priv_c ", hex.EncodeToString(crypto.FromECDSA(newPrivateKey_c)), " pubKey_c ", pubKey_c.String()) + newPrivateKey_d, _ = crypto.GenerateKey() + pubKey_d = common.BytesToPubKey(crypto.FromECDSAPub(&newPrivateKey_d.PublicKey)) + d_author_0_priv = newPrivateKey_d + fmt.Println("priv_d ", hex.EncodeToString(crypto.FromECDSA(newPrivateKey_d)), " pubKey_d ", pubKey_d.String()) + balance, _ := testcommon.GetAccountBalanceByID(from, assetID) balance.Div(balance, big.NewInt(10)) - aca = common.Name(fmt.Sprintf("accounta%d", nonce)) - acb = common.Name(fmt.Sprintf("accountb%d", nonce)) - acc = common.Name(fmt.Sprintf("accountc%d", nonce)) + aca = common.Name(fmt.Sprintf("fcoinaccounta%d", nonce)) + acb = common.Name(fmt.Sprintf("fcoinaccountb%d", nonce)) + acc = common.Name(fmt.Sprintf("fcoinaccountc%d", nonce)) + acd = common.Name(fmt.Sprintf("fcoinaccountd%d", nonce)) key := types.MakeKeyPair(privateKey, []uint64{0}) acct := &accountmanager.CreateAccountAction{ @@ -97,7 +109,7 @@ func generateAccount() { PublicKey: pubKey_a, } b, _ := rlp.EncodeToBytes(acct) - sendTransferTx(types.CreateAccount, from, to, nonce, assetID, balance, b, []*types.KeyPair{key}) + sendTransferTx(types.CreateAccount, from, to, nonce, assetID, balance, b, []*types.KeyPair{key}, nil, nil) acct = &accountmanager.CreateAccountAction{ AccountName: acb, @@ -105,7 +117,7 @@ func generateAccount() { PublicKey: pubKey_b, } b, _ = rlp.EncodeToBytes(acct) - sendTransferTx(types.CreateAccount, from, to, nonce+1, assetID, balance, b, []*types.KeyPair{key}) + sendTransferTx(types.CreateAccount, from, to, nonce+1, assetID, balance, b, []*types.KeyPair{key}, nil, nil) acct = &accountmanager.CreateAccountAction{ AccountName: acc, @@ -113,27 +125,39 @@ func generateAccount() { PublicKey: pubKey_c, } b, _ = rlp.EncodeToBytes(acct) - sendTransferTx(types.CreateAccount, from, to, nonce+2, assetID, balance, b, []*types.KeyPair{key}) + sendTransferTx(types.CreateAccount, from, to, nonce+2, assetID, balance, b, []*types.KeyPair{key}, nil, nil) + + acct = &accountmanager.CreateAccountAction{ + AccountName: acd, + Founder: acd, + PublicKey: pubKey_d, + } + b, _ = rlp.EncodeToBytes(acct) + sendTransferTx(types.CreateAccount, from, to, nonce+3, assetID, balance, b, []*types.KeyPair{key}, nil, nil) for { time.Sleep(10 * time.Second) aexist, _ := testcommon.CheckAccountIsExist(aca) bexist, _ := testcommon.CheckAccountIsExist(acb) cexist, _ := testcommon.CheckAccountIsExist(acc) + dexist, _ := testcommon.CheckAccountIsExist(acd) acaAccount, _ := testcommon.GetAccountByName(aca) acbAccount, _ := testcommon.GetAccountByName(acb) accAccount, _ := testcommon.GetAccountByName(acc) + acdAccount, _ := testcommon.GetAccountByName(acd) + fmt.Println("acaAccount version hash", acaAccount.AuthorVersion.Hex()) fmt.Println("acbAccount version hash", acbAccount.AuthorVersion.Hex()) fmt.Println("accAccount version hash", accAccount.AuthorVersion.Hex()) + fmt.Println("accAccount version hash", acdAccount.AuthorVersion.Hex()) - if aexist && bexist && cexist { + if aexist && bexist && cexist && dexist { break } } - fmt.Println("aca ", aca, " acb ", acb, " acc ", acc) + fmt.Println("aca ", aca, " acb ", acb, " acc ", acc, " acd ", acd) } func init() { @@ -170,7 +194,8 @@ func addAuthorsForAca() { return } key := types.MakeKeyPair(newPrivateKey_a, []uint64{0}) - sendTransferTx(types.UpdateAccountAuthor, aca, to, aNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}) + + sendTransferTx(types.UpdateAccountAuthor, aca, to, aNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}, nil, nil) } func addAuthorsForAcb() { @@ -192,7 +217,7 @@ func addAuthorsForAcb() { return } key := types.MakeKeyPair(newPrivateKey_b, []uint64{0}) - sendTransferTx(types.UpdateAccountAuthor, acb, to, bNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}) + sendTransferTx(types.UpdateAccountAuthor, acb, to, bNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}, nil, nil) } func addAuthorsForAcc() { @@ -216,7 +241,7 @@ func addAuthorsForAcc() { return } key := types.MakeKeyPair(newPrivateKey_c, []uint64{0}) - sendTransferTx(types.UpdateAccountAuthor, acc, to, cNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}) + sendTransferTx(types.UpdateAccountAuthor, acc, to, cNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key}, nil, nil) } func transferFromA2B() { @@ -229,7 +254,7 @@ func transferFromA2B() { key_1_2 := types.MakeKeyPair(b_author_2_priv, []uint64{1, 2}) aNonce++ - sendTransferTx(types.Transfer, aca, to, aNonce, assetID, big.NewInt(1), nil, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2}) + sendTransferTx(types.Transfer, aca, to, aNonce, assetID, big.NewInt(1), nil, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2}, nil, nil) } func modifyAUpdateAUthorThreshold() { @@ -250,7 +275,28 @@ func modifyAUpdateAUthorThreshold() { } aNonce++ - sendTransferTx(types.UpdateAccountAuthor, aca, to, aNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2, key_0}) + sendTransferTx(types.UpdateAccountAuthor, aca, to, aNonce, assetID, big.NewInt(0), input, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2, key_0}, nil, nil) +} + +func transferFromA2BWithBAsPayer() { + key_1_0 := types.MakeKeyPair(b_author_0_priv, []uint64{1, 0}) + key_1_1_0 := types.MakeKeyPair(c_author_0_priv, []uint64{1, 1, 0}) + key_1_1_1 := types.MakeKeyPair(c_author_1_priv, []uint64{1, 1, 1}) + key_1_1_2 := types.MakeKeyPair(c_author_2_priv, []uint64{1, 1, 2}) + key_2 := types.MakeKeyPair(a_author_2_priv, []uint64{2}) + key_3 := types.MakeKeyPair(a_author_3_priv, []uint64{3}) + key_1_2 := types.MakeKeyPair(b_author_2_priv, []uint64{1, 2}) + + gasPrice, _ := testcommon.GasPrice() + fp := &types.FeePayer{ + GasPrice: gasPrice, + Payer: acd, + Sign: &types.Signature{0, make([]*types.SignData, 0)}, + } + payerKey := types.MakeKeyPair(newPrivateKey_d, []uint64{0}) + + aNonce++ + sendTransferTx(types.Transfer, aca, to, aNonce, assetID, big.NewInt(1), nil, []*types.KeyPair{key_1_0, key_1_1_0, key_1_1_1, key_1_1_2, key_2, key_3, key_1_2}, fp, []*types.KeyPair{payerKey}) } func main() { @@ -276,11 +322,16 @@ func main() { transferFromA2B() modifyAUpdateAUthorThreshold() + + transferFromA2BWithBAsPayer() } -func sendTransferTx(txType types.ActionType, from, to common.Name, nonce, assetID uint64, value *big.Int, input []byte, keys []*types.KeyPair) { +func sendTransferTx(txType types.ActionType, from, to common.Name, nonce, assetID uint64, value *big.Int, input []byte, keys []*types.KeyPair, fp *types.FeePayer, payerKeys []*types.KeyPair) { action := types.NewAction(txType, from, to, nonce, assetID, gasLimit, value, input, nil) gasprice, _ := testcommon.GasPrice() + if fp != nil { + gasprice = big.NewInt(0) + } tx := types.NewTransaction(0, gasprice, action) signer := types.MakeSigner(big.NewInt(1)) @@ -289,6 +340,13 @@ func sendTransferTx(txType types.ActionType, from, to common.Name, nonce, assetI jww.ERROR.Fatalln(err) } + if fp != nil { + err = types.SignPayerActionWithMultiKey(action, tx, signer, fp, 0, payerKeys) + if err != nil { + jww.ERROR.Fatalln(err) + } + } + rawtx, err := rlp.EncodeToBytes(tx) if err != nil { jww.ERROR.Fatalln(err) diff --git a/txpool/config.go b/txpool/config.go index 328291d0..5b661af1 100644 --- a/txpool/config.go +++ b/txpool/config.go @@ -48,7 +48,7 @@ type Config struct { var DefaultTxPoolConfig = &Config{ Journal: "transactions.rlp", Rejournal: time.Hour, - PriceLimit: 1000000000, + PriceLimit: 100000000000, PriceBump: 10, AccountSlots: 128, GlobalSlots: 4096, diff --git a/txpool/error.go b/txpool/error.go index abb05b26..9d34c5d8 100644 --- a/txpool/error.go +++ b/txpool/error.go @@ -24,10 +24,10 @@ var ( // ErrInvalidSender is returned if the transaction contains an invalid signature. ErrInvalidSender = errors.New("invalid sender") + ErrPayerTx = errors.New("payer is exist") // ErrNonceTooLow is returned if the nonce of a transaction is lower than the // one present in the local chain. ErrNonceTooLow = errors.New("nonce too low") - // ErrUnderpriced is returned if a transaction's gas price is below the minimum // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") diff --git a/txpool/test_utils.go b/txpool/test_utils.go index ae14c536..940fd2c3 100644 --- a/txpool/test_utils.go +++ b/txpool/test_utils.go @@ -84,6 +84,32 @@ func transaction(nonce uint64, from, to common.Name, gaslimit uint64, key *ecdsa return pricedTransaction(nonce, from, to, gaslimit, big.NewInt(1), key) } +func extendTransaction(nonce uint64, payer, from, to common.Name, gasLimit uint64, key, payerKey *ecdsa.PrivateKey) *types.Transaction { + + fp := &types.FeePayer{ + GasPrice: big.NewInt(100), + Payer: payer, + Sign: &types.Signature{0, make([]*types.SignData, 0)}, + } + + action := types.NewAction(types.Transfer, from, to, nonce, 0, gasLimit, big.NewInt(100), nil, nil) + tx := types.NewTransaction(0, big.NewInt(0), action) + signer := types.MakeSigner(params.DefaultChainconfig.ChainID) + keyPair := types.MakeKeyPair(key, []uint64{0}) + err := types.SignActionWithMultiKey(action, tx, signer, 0, []*types.KeyPair{keyPair}) + if err != nil { + panic(err) + } + + payerKeyPair := types.MakeKeyPair(payerKey, []uint64{0}) + err = types.SignPayerActionWithMultiKey(action, tx, signer, fp, 0, []*types.KeyPair{payerKeyPair}) + if err != nil { + panic(err) + } + + return tx +} + func pricedTransaction(nonce uint64, from, to common.Name, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey) *types.Transaction { tx := newTx(gasprice, newAction(nonce, from, to, big.NewInt(100), gaslimit, nil)) keyPair := types.MakeKeyPair(key, []uint64{0}) diff --git a/txpool/txpool.go b/txpool/txpool.go index 9c98083c..e3ceb9c5 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -651,6 +651,7 @@ func (tp *TxPool) local() map[common.Name][]*types.Transaction { func (tp *TxPool) validateTx(tx *types.Transaction, local bool) error { validateAction := func(tx *types.Transaction, action *types.Action) error { from := action.Sender() + // Drop non-local transactions under our own minimal accepted gas price local = local || tp.locals.contains(from) // account may be local even if the transaction arrived from the network if !local && tp.gasPrice.Cmp(tx.GasPrice()) > 0 { @@ -666,10 +667,27 @@ func (tp *TxPool) validateTx(tx *types.Transaction, local bool) error { return ErrNonceTooLow } - // Transactor should have enough funds to cover the gas costs - balance, err := tp.curAccountManager.GetAccountBalanceByID(from, tx.GasAssetID(), 0) - if err != nil { - return err + // wait fork successed, remove it + if action.PayerIsExist() && tp.chain.CurrentBlock().CurForkID() < params.ForkID4 { + return fmt.Errorf("This type of transaction: %v is not currently supported", tx.Hash().Hex()) + } + + var balance *big.Int + if tx.PayerExist() { + // Transactor should have enough funds to cover the gas costs + balance, err = tp.curAccountManager.GetAccountBalanceByID(action.Payer(), tx.GasAssetID(), 0) + if err != nil { + return err + } + } else { + if action.PayerIsExist() { + return ErrPayerTx + } + // Transactor should have enough funds to cover the gas costs + balance, err = tp.curAccountManager.GetAccountBalanceByID(from, tx.GasAssetID(), 0) + if err != nil { + return err + } } gascost := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(action.Gas())) @@ -685,7 +703,9 @@ func (tp *TxPool) validateTx(tx *types.Transaction, local bool) error { value := action.Value() if tp.config.GasAssetID == action.AssetID() { - value.Add(value, gascost) + if !tx.PayerExist() { + value.Add(value, gascost) + } } if balance.Cmp(value) < 0 { @@ -751,7 +771,6 @@ func (tp *TxPool) add(tx *types.Transaction, local bool) (bool, error) { } // If the transaction is replacing an already pending one, do directly - // todo Change action from := tx.GetActions()[0].Sender() if list := tp.pending[from]; list != nil && list.Overlaps(tx) { // Nonce already pending, check if required price bump is met @@ -764,6 +783,7 @@ func (tp *TxPool) add(tx *types.Transaction, local bool) (bool, error) { tp.all.Remove(old.Hash()) tp.priced.Removed(1) } + tp.all.Add(tx) tp.priced.Put(tx) tp.journalTx(from, tx) @@ -913,13 +933,13 @@ func (tp *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { // Cache senders in transactions before obtaining lock (pool.signer is immutable) for index, tx := range txs { // If the transaction is already known, discard it - if tp.all.Get(tx.Hash()) != nil { + if storgeTx := tp.all.Get(tx.Hash()); storgeTx != nil && tx.GasPrice().Cmp(storgeTx.GasPrice()) == 0 { log.Trace("Discarding already known transaction", "hash", tx.Hash()) errs[index] = errors.New("already known transaction") continue } - if err := tx.Check(tp.chain.Config()); err != nil { + if err := tx.Check(tp.chain.CurrentBlock().Header().CurForkID(), tp.chain.Config()); err != nil { log.Trace("add txs check ", "err", err, "hash", tx.Hash()) errs[index] = fmt.Errorf("transaction check err: %v", err) continue diff --git a/txpool/txpool_test.go b/txpool/txpool_test.go index d471fb38..69c554c5 100644 --- a/txpool/txpool_test.go +++ b/txpool/txpool_test.go @@ -44,6 +44,67 @@ func TestConfigCheck(t *testing.T) { assert.Equal(t, cfg.check(), *DefaultTxPoolConfig) } +// func TestAddPayerTx(t *testing.T) { +// var ( +// statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) +// manager, _ = am.NewAccountManager(statedb) +// fname = common.Name("fromname") +// tname = common.Name("totestname") +// fkey = generateAccount(t, fname, manager) +// tkey = generateAccount(t, tname, manager) +// asset = asset.NewAsset(statedb) +// ) + +// // issue asset +// if _, err := asset.IssueAsset("ft", 0, 0, "zz", new(big.Int).SetUint64(params.Fractal), 10, common.Name(""), fname, new(big.Int).SetUint64(params.Fractal), common.Name(""), ""); err != nil { +// t.Fatal(err) +// } + +// // add balance +// if err := manager.AddAccountBalanceByName(fname, "ft", new(big.Int).SetUint64(params.Fractal)); err != nil { +// t.Fatal(err) +// } + +// if err := manager.AddAccountBalanceByName(tname, "ft", new(big.Int).SetUint64(params.Fractal)); err != nil { +// t.Fatal(err) +// } + +// blockchain := &testBlockChain{statedb, 1000000000, new(event.Feed)} +// tx0 := pricedTransaction(0, fname, tname, 109000, big.NewInt(0), fkey) +// tx1 := extendTransaction(0, tname, fname, tname, 109000, fkey, tkey) + +// params.DefaultChainconfig.SysTokenID = 0 + +// pool := New(testTxPoolConfig, params.DefaultChainconfig, blockchain) +// defer pool.Stop() + +// nonce, err := pool.State().GetNonce(fname) +// if err != nil { +// t.Fatal("Invalid getNonce ", err) +// } +// if nonce != 0 { +// t.Fatalf("Invalid nonce, want 0, got %d", nonce) +// } + +// errs := pool.addRemotesSync([]*types.Transaction{tx0, tx1}) + +// t.Log(errs) +// nonce, err = pool.State().GetNonce(fname) +// if err != nil { +// t.Fatal("Invalid getNonce ", err) +// } + +// if nonce != 1 { +// t.Fatalf("Invalid nonce, want 1, got %d", nonce) +// } + +// result := pool.Get(tx1.Hash()) + +// if !result.PayerExist() { +// t.Fatal("add payer tx failed") +// } +// } + // This test simulates a scenario where a new block is imported during a // state reset and tests whether the pending state is in sync with the // block head event that initiated the resetState(). diff --git a/txpool/utils.go b/txpool/utils.go index 0353d24c..fc14bdd6 100644 --- a/txpool/utils.go +++ b/txpool/utils.go @@ -101,6 +101,9 @@ func IntrinsicGas(accountDB *accountmanager.AccountManager, action *types.Action gas += (uint64(len(action.GetSign()) - 1)) * gasTable.SignGas } + payerSignLen := len(action.GetFeePayerSign()) + gas += uint64(payerSignLen) * gasTable.SignGas + if action.Value().Sign() != 0 { gas += receiptGasFunc(action) } diff --git a/types/action.go b/types/action.go index 6d47fd3a..61276d76 100644 --- a/types/action.go +++ b/types/action.go @@ -80,6 +80,9 @@ const ( RefundCandidate // VoteCandidate repesents voter vote candidate action. VoteCandidate + + // UpdateCandidatePubKey repesents update candidate action. + UpdateCandidatePubKey ) const ( @@ -108,6 +111,12 @@ type SignData struct { Index []uint64 } +type FeePayer struct { + GasPrice *big.Int + Payer common.Name + Sign *Signature +} + type actionData struct { AType ActionType Nonce uint64 @@ -118,16 +127,20 @@ type actionData struct { Amount *big.Int Payload []byte Remark []byte + Sign *Signature - Sign *Signature + Extend []rlp.RawValue `rlp:"tail"` } // Action represents an entire action in the transaction. type Action struct { data actionData // cache + fp *FeePayer hash atomic.Value + extendHash atomic.Value senderPubkeys atomic.Value + payerPubkeys atomic.Value author atomic.Value } @@ -147,6 +160,7 @@ func NewAction(actionType ActionType, from, to common.Name, nonce, assetID, gasL Payload: payload, Remark: remark, Sign: &Signature{0, make([]*SignData, 0)}, + Extend: make([]rlp.RawValue, 0), } if amount != nil { data.Amount.Set(amount) @@ -166,8 +180,40 @@ func (a *Action) GetSignParent() uint64 { return a.data.Sign.ParentIndex } +func (a *Action) PayerIsExist() bool { + return a.fp != nil +} + +func (a *Action) PayerGasPrice() *big.Int { + if a.fp != nil { + return a.fp.GasPrice + } + return nil +} + +func (a *Action) Payer() common.Name { + if a.fp != nil { + return a.fp.Payer + } + return common.Name("") +} + +func (a *Action) PayerSignature() *Signature { + if a.fp != nil { + return a.fp.Sign + } + return nil +} + +func (a *Action) GetFeePayerSign() []*SignData { + if a.fp != nil { + return a.fp.Sign.SignData + } + return nil +} + // Check the validity of all fields -func (a *Action) Check(conf *params.ChainConfig) error { +func (a *Action) Check(fid uint64, conf *params.ChainConfig) error { //check To switch a.Type() { case CreateContract: @@ -203,6 +249,11 @@ func (a *Action) Check(conf *params.ChainConfig) error { } case Transfer: //dpos + case UpdateCandidatePubKey: + if fid < params.ForkID4 { + return fmt.Errorf("Receipt undefined") + } + fallthrough case RegCandidate: fallthrough case UpdateCandidate: @@ -279,14 +330,50 @@ func (a *Action) Gas() uint64 { return a.data.GasLimit } // Value returns action's Value. func (a *Action) Value() *big.Int { return new(big.Int).Set(a.data.Amount) } +func (a *Action) Extend() []rlp.RawValue { return a.data.Extend } + +// IgnoreExtend returns ignore extend +func (a *Action) IgnoreExtend() []interface{} { + return []interface{}{ + a.data.AType, + a.data.Nonce, + a.data.AssetID, + a.data.From, + a.data.To, + a.data.GasLimit, + a.data.Amount, + a.data.Payload, + a.data.Remark, + a.data.Sign, + } +} + // EncodeRLP implements rlp.Encoder func (a *Action) EncodeRLP(w io.Writer) error { + if a.fp != nil { + value, err := rlp.EncodeToBytes(a.fp) + if err != nil { + return err + } + a.data.Extend = []rlp.RawValue{value} + } + return rlp.Encode(w, &a.data) } // DecodeRLP implements rlp.Decoder func (a *Action) DecodeRLP(s *rlp.Stream) error { - return s.Decode(&a.data) + if err := s.Decode(&a.data); err != nil { + return err + } + + if len(a.data.Extend) != 0 { + a.fp = new(FeePayer) + return rlp.DecodeBytes(a.data.Extend[0], a.fp) + + } + + return nil } // ChainID returns which chain id this action was signed for (if at all) @@ -299,11 +386,21 @@ func (a *Action) Hash() common.Hash { if hash := a.hash.Load(); hash != nil { return hash.(common.Hash) } - v := RlpHash(a) + v := RlpHash(a.IgnoreExtend()) a.hash.Store(v) return v } +// ExtendHash hashes the RLP encoding of action. +func (a *Action) ExtendHash() common.Hash { + if hash := a.extendHash.Load(); hash != nil { + return hash.(common.Hash) + } + v := RlpHash(a) + a.extendHash.Store(v) + return v +} + // WithSignature returns a new transaction with the given signature. func (a *Action) WithSignature(signer Signer, sig []byte, index []uint64) error { r, s, v, err := signer.SignatureValues(sig) @@ -314,11 +411,32 @@ func (a *Action) WithSignature(signer Signer, sig []byte, index []uint64) error return nil } -// WithSignature returns a new transaction with the given signature. +// WithParentIndex returns a new transaction with the given signature. func (a *Action) WithParentIndex(parentIndex uint64) { a.data.Sign.ParentIndex = parentIndex } +func (f *FeePayer) WithSignature(signer Signer, sig []byte, index []uint64) error { + r, s, v, err := signer.SignatureValues(sig) + if err != nil { + return err + } + f.Sign.SignData = append(f.Sign.SignData, &SignData{R: r, S: s, V: v, Index: index}) + return nil +} + +func (f *FeePayer) WithParentIndex(parentIndex uint64) { + f.Sign.ParentIndex = parentIndex +} + +func (f *FeePayer) GetSignParent() uint64 { + return f.Sign.ParentIndex +} + +func (f *FeePayer) GetSignIndex(i uint64) []uint64 { + return f.Sign.SignData[i].Index +} + // RPCAction represents a action that will serialize to the RPC representation of a action. type RPCAction struct { Type uint64 `json:"type"` @@ -355,6 +473,57 @@ func (a *Action) NewRPCAction(index uint64) *RPCAction { } } +type RPCActionWithPayer struct { + Type uint64 `json:"type"` + Nonce uint64 `json:"nonce"` + From common.Name `json:"from"` + To common.Name `json:"to"` + AssetID uint64 `json:"assetID"` + GasLimit uint64 `json:"gas"` + Amount *big.Int `json:"value"` + Remark hexutil.Bytes `json:"remark"` + Payload hexutil.Bytes `json:"payload"` + Hash common.Hash `json:"actionHash"` + ActionIdex uint64 `json:"actionIndex"` + Payer common.Name `json:"payer"` + PayerGasPrice *big.Int `json:"payerGasPrice"` + ParentIndex uint64 `json:"parentIndex"` + PayerParentIndex uint64 `json:"payerParentIndex"` +} + +func (a *RPCActionWithPayer) SetHash(hash common.Hash) { + a.Hash = hash +} + +func (a *Action) NewRPCActionWithPayer(index uint64) *RPCActionWithPayer { + var payer common.Name + var price *big.Int + if a.fp != nil { + payer = a.fp.Payer + price = a.fp.GasPrice + } + ap := &RPCActionWithPayer{ + Type: uint64(a.Type()), + Nonce: a.Nonce(), + From: a.Sender(), + To: a.Recipient(), + AssetID: a.AssetID(), + GasLimit: a.Gas(), + Amount: a.Value(), + Remark: hexutil.Bytes(a.Remark()), + Payload: hexutil.Bytes(a.Data()), + Hash: a.Hash(), + ActionIdex: index, + Payer: payer, + PayerGasPrice: price, + ParentIndex: a.GetSignParent(), + } + if a.fp != nil { + ap.PayerParentIndex = a.fp.GetSignParent() + } + return ap +} + // deriveChainID derives the chain id from the given v parameter func deriveChainID(v *big.Int) *big.Int { v = new(big.Int).Sub(v, big.NewInt(35)) diff --git a/types/action_test.go b/types/action_test.go index 87f26b59..a754486f 100644 --- a/types/action_test.go +++ b/types/action_test.go @@ -99,8 +99,8 @@ func TestActionEncodeAndDecode(t *testing.T) { if err := rlp.Decode(bytes.NewReader(actionBytes), &actAction); err != nil { t.Fatal(err) } - assert.Equal(t, testAction, actAction) + } func TestAction_Check(t *testing.T) { @@ -114,7 +114,7 @@ func TestAction_Check(t *testing.T) { t.Fatal(err) } - if err := actAction.Check(params.DefaultChainconfig); err != nil { + if err := actAction.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction_CheckValue err, wantErr %v", true) } @@ -129,7 +129,7 @@ func TestAction_Check(t *testing.T) { t.Fatal(err) } - if err := actAction2.Check(params.DefaultChainconfig); err == nil { + if err := actAction2.Check(params.NextForkID, params.DefaultChainconfig); err == nil { t.Errorf("TestAction2_CheckValue err, wantErr %v", false) } @@ -144,7 +144,7 @@ func TestAction_Check(t *testing.T) { t.Fatal(err) } - if err := actAction3.Check(params.DefaultChainconfig); err != nil { + if err := actAction3.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } @@ -159,7 +159,7 @@ func TestAction_Check(t *testing.T) { t.Fatal(err) } - if err := actAction3.Check(params.DefaultChainconfig); err != nil { + if err := actAction3.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } @@ -174,7 +174,7 @@ func TestAction_Check(t *testing.T) { t.Fatal(err) } - if err := actAction3.Check(params.DefaultChainconfig); err != nil { + if err := actAction3.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } } @@ -252,23 +252,23 @@ func TestAction_Check2(t *testing.T) { // t.Fatal(err) // } - if err := testAction10.Check(params.DefaultChainconfig); err != nil { + if err := testAction10.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } - if err := testAction11.Check(params.DefaultChainconfig); err != nil { + if err := testAction11.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } - if err := testAction12.Check(params.DefaultChainconfig); err != nil { + if err := testAction12.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } - if err := testAction13.Check(params.DefaultChainconfig); err != nil { + if err := testAction13.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } - if err := testAction14.Check(params.DefaultChainconfig); err != nil { + if err := testAction14.Check(params.NextForkID, params.DefaultChainconfig); err != nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } @@ -347,23 +347,23 @@ func TestAction_Check3(t *testing.T) { // t.Fatal(err) // } - if err := testAction20.Check(params.DefaultChainconfig); err == nil { + if err := testAction20.Check(params.NextForkID, params.DefaultChainconfig); err == nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } - if err := testAction21.Check(params.DefaultChainconfig); err == nil { + if err := testAction21.Check(params.NextForkID, params.DefaultChainconfig); err == nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } - if err := testAction22.Check(params.DefaultChainconfig); err == nil { + if err := testAction22.Check(params.NextForkID, params.DefaultChainconfig); err == nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } - if err := testAction23.Check(params.DefaultChainconfig); err == nil { + if err := testAction23.Check(params.NextForkID, params.DefaultChainconfig); err == nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } - if err := testAction24.Check(params.DefaultChainconfig); err == nil { + if err := testAction24.Check(params.NextForkID, params.DefaultChainconfig); err == nil { t.Errorf("TestAction3_CheckValue err, wantErr %v", false) } diff --git a/types/block.go b/types/block.go index 61af0727..3e69bf2f 100644 --- a/types/block.go +++ b/types/block.go @@ -26,6 +26,7 @@ import ( "sync/atomic" "github.com/fractalplatform/fractal/common" + "github.com/fractalplatform/fractal/params" "github.com/fractalplatform/fractal/utils/rlp" ) @@ -93,17 +94,22 @@ func NewBlock(header *Header, txs []*Transaction, receipts []*Receipt) *Block { } b := &Block{Head: header} - b.Head.TxsRoot = DeriveTxsMerkleRoot(txs) + + if header.ForkID.Cur >= params.ForkID4 { + b.Head.TxsRoot = DeriveExtensTxsMerkleRoot(txs) + } else { + b.Head.TxsRoot = DeriveTxsMerkleRoot(txs) + } + b.Head.ReceiptsRoot = DeriveReceiptsMerkleRoot(receipts) b.Txs = make([]*Transaction, len(txs)) copy(b.Txs, txs) b.Head.Bloom = CreateBloom(receipts) - return b } -// NewBlockWithHeader creates a block with the given header data. The +// NewBlockWithHeader create s a block with the given header data. The // header data is copied, changes to header and to the field values // will not affect the block. func NewBlockWithHeader(header *Header) *Block { @@ -288,6 +294,15 @@ func (bs blockSorter) Less(i, j int) bool { return bs.by(bs.blocks[i], bs.blocks // Number represents block sort by number. func Number(b1, b2 *Block) bool { return b1.Head.Number.Cmp(b2.Head.Number) < 0 } +// DeriveExtensTxsMerkleRoot returns Extens txs merkle tree root hash. +func DeriveExtensTxsMerkleRoot(txs []*Transaction) common.Hash { + var txHashs []common.Hash + for i := 0; i < len(txs); i++ { + txHashs = append(txHashs, txs[i].ExtensHash()) + } + return common.MerkleRoot(txHashs) +} + // DeriveTxsMerkleRoot returns txs merkle tree root hash. func DeriveTxsMerkleRoot(txs []*Transaction) common.Hash { var txHashs []common.Hash diff --git a/types/receipt.go b/types/receipt.go index fc0b0865..6f6a7cac 100644 --- a/types/receipt.go +++ b/types/receipt.go @@ -17,6 +17,8 @@ package types import ( + "math/big" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/utils/rlp" @@ -67,6 +69,36 @@ func (a *ActionResult) NewRPCActionResult(aType ActionType) *RPCActionResult { } } +type RPCActionResultWithPayer struct { + ActionType uint64 `json:"actionType"` + Status uint64 `json:"status"` + Index uint64 `json:"index"` + GasUsed uint64 `json:"gasUsed"` + GasAllot []*GasDistribution `json:"gasAllot"` + Error string `json:"error"` + Payer common.Name `json:"payer"` + PayerGasPrice *big.Int `json:"payerGasPrice"` +} + +// NewRPCActionResult returns a ActionResult that will serialize to the RPC. +func (a *ActionResult) NewRPCActionResultWithPayer(action *Action, gasPrice *big.Int) *RPCActionResultWithPayer { + var payer = action.Sender() + var price = gasPrice + if action.fp != nil { + payer = action.fp.Payer + } + return &RPCActionResultWithPayer{ + ActionType: uint64(action.Type()), + Status: a.Status, + Index: a.Index, + GasUsed: a.GasUsed, + GasAllot: a.GasAllot, + Error: a.Error, + Payer: payer, + PayerGasPrice: price, + } +} + // Receipt represents the results of a transaction. type Receipt struct { PostState []byte @@ -137,6 +169,48 @@ func (r *Receipt) NewRPCReceipt(blockHash common.Hash, blockNumber uint64, index return result } +// RPCReceipt that will serialize to the RPC representation of a Receipt. +type RPCReceiptWithPayer struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber uint64 `json:"blockNumber"` + Hash common.Hash `json:"txHash"` + TransactionIndex uint64 `json:"transactionIndex"` + PostState hexutil.Bytes `json:"postState"` + ActionResults []*RPCActionResultWithPayer `json:"actionResults"` + CumulativeGasUsed uint64 `json:"cumulativeGasUsed"` + TotalGasUsed uint64 `json:"totalGasUsed"` + Bloom Bloom `json:"logsBloom"` + Logs []*RPCLog `json:"logs"` +} + +// NewRPCReceipt returns a Receipt that will serialize to the RPC. +func (r *Receipt) NewRPCReceiptWithPayer(blockHash common.Hash, blockNumber uint64, index uint64, tx *Transaction) *RPCReceiptWithPayer { + result := &RPCReceiptWithPayer{ + BlockHash: blockHash, + BlockNumber: blockNumber, + Hash: tx.Hash(), + TransactionIndex: index, + PostState: hexutil.Bytes(r.PostState), + CumulativeGasUsed: r.CumulativeGasUsed, + TotalGasUsed: r.TotalGasUsed, + Bloom: r.Bloom, + } + + var rpcActionResults []*RPCActionResultWithPayer + for i, a := range tx.GetActions() { + rpcActionResults = append(rpcActionResults, r.ActionResults[i].NewRPCActionResultWithPayer(a, tx.GasPrice())) + } + result.ActionResults = rpcActionResults + + var rlogs []*RPCLog + for _, l := range r.Logs { + rlogs = append(rlogs, l.NewRPCLog()) + } + result.Logs = rlogs + + return result +} + // ConsensusReceipt returns consensus encoding of a receipt. func (r *Receipt) ConsensusReceipt() *Receipt { result := &Receipt{ diff --git a/types/signer.go b/types/signer.go index c06550b2..616907d6 100644 --- a/types/signer.go +++ b/types/signer.go @@ -24,6 +24,7 @@ import ( "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/crypto" + "github.com/fractalplatform/fractal/utils/rlp" ) var ( @@ -88,6 +89,47 @@ func RecoverMultiKey(signer Signer, a *Action, tx *Transaction) ([]common.PubKey return pubKeys, nil } +func SignPayerActionWithMultiKey(a *Action, tx *Transaction, s Signer, feePayer *FeePayer, parentIndex uint64, keys []*KeyPair) error { + a.fp = feePayer + h := s.FeePayerHash(tx) + for _, key := range keys { + sig, err := crypto.Sign(h[:], key.priv) + if err != nil { + return err + } + + err = feePayer.WithSignature(s, sig, key.index) + if err != nil { + return err + } + } + feePayer.WithParentIndex(parentIndex) + + if value, err := rlp.EncodeToBytes(feePayer); err != nil { + return err + } else { + a.data.Extend = append(a.data.Extend, value) + } + + return nil +} + +func RecoverPayerMultiKey(signer Signer, a *Action, tx *Transaction) ([]common.PubKey, error) { + if sc := a.payerPubkeys.Load(); sc != nil { + sigCache := sc.(sigCache) + if sigCache.signer.Equal(signer) { + return sigCache.pubKeys, nil + } + } + + pubKeys, err := signer.PayerPubKeys(a, tx) + if err != nil { + return []common.PubKey{}, err + } + a.payerPubkeys.Store(sigCache{signer: signer, pubKeys: pubKeys}) + return pubKeys, nil +} + func StoreAuthorCache(a *Action, authorVersion map[common.Name]common.Hash) { a.author.Store(authorVersion) } @@ -147,6 +189,25 @@ func (s Signer) PubKeys(a *Action, tx *Transaction) ([]common.PubKey, error) { return pubKeys, nil } +func (s Signer) PayerPubKeys(a *Action, tx *Transaction) ([]common.PubKey, error) { + if len(a.fp.Sign.SignData) == 0 { + return nil, ErrSignEmpty + } + + var pubKeys []common.PubKey + for _, sign := range a.fp.Sign.SignData { + V := new(big.Int).Sub(sign.V, s.chainIDMul) + V.Sub(V, big8) + data, err := recoverPlain(s.FeePayerHash(tx), sign.R, sign.S, V) + if err != nil { + return nil, err + } + pubKey := common.BytesToPubKey(data) + pubKeys = append(pubKeys, pubKey) + } + return pubKeys, nil +} + // SignatureValues returns a new transaction with the given signature. This signature // needs to be in the [R || S || V] format where V is 0 or 1. func (s Signer) SignatureValues(sig []byte) (R, S, V *big.Int, err error) { @@ -190,6 +251,33 @@ func (s Signer) Hash(tx *Transaction) common.Hash { }) } +func (s Signer) FeePayerHash(tx *Transaction) common.Hash { + actionHashs := make([]common.Hash, len(tx.GetActions())) + for i, a := range tx.GetActions() { + hash := RlpHash([]interface{}{ + a.data.From, + a.data.AType, + a.data.Nonce, + a.data.To, + a.data.GasLimit, + a.data.Amount, + a.data.Payload, + a.data.AssetID, + a.data.Remark, + a.fp.Payer, + a.fp.GasPrice, + s.chainID, uint(0), uint(0), + }) + actionHashs[i] = hash + } + + return RlpHash([]interface{}{ + common.MerkleRoot(actionHashs), + tx.gasAssetID, + tx.gasPrice, + }) +} + func recoverPlain(sighash common.Hash, R, S, Vb *big.Int) ([]byte, error) { if Vb.BitLen() > 8 { return nil, ErrInvalidSig diff --git a/types/signer_test.go b/types/signer_test.go index 1c487e94..824f002f 100644 --- a/types/signer_test.go +++ b/types/signer_test.go @@ -62,6 +62,49 @@ func TestSigningMultiKey(t *testing.T) { } } +func TestSigningPayerMultiKey(t *testing.T) { + keys := make([]*KeyPair, 0) + pubs := make([]common.PubKey, 0) + for i := 0; i < 4; i++ { + key, _ := crypto.GenerateKey() + exp := crypto.FromECDSAPub(&key.PublicKey) + keys = append(keys, &KeyPair{priv: key, index: []uint64{uint64(i)}}) + pubs = append(pubs, common.BytesToPubKey(exp)) + } + signer := NewSigner(big.NewInt(1)) + fp := &FeePayer{ + GasPrice: big.NewInt(0), + Payer: testTx.GetActions()[0].Recipient(), + Sign: &Signature{0, make([]*SignData, 0)}, + } + if err := SignPayerActionWithMultiKey(testTx.GetActions()[0], testTx, signer, fp, 0, keys); err != nil { + t.Fatal(err) + } + + pubkeys, err := RecoverPayerMultiKey(signer, testTx.GetActions()[0], testTx) + if err != nil { + t.Fatal(err) + } + + for i, pubkey := range pubkeys { + if pubkey.Compare(pubs[i]) != 0 { + t.Errorf("exected from and pubkey to be equal. Got %x want %x", pubkey, pubs[i]) + } + } + + //test cache + pubkeys, err = RecoverPayerMultiKey(signer, testTx.GetActions()[0], testTx) + if err != nil { + t.Fatal(err) + } + + for i, pubkey := range pubkeys { + if pubkey.Compare(pubs[i]) != 0 { + t.Errorf("exected from and pubkey to be equal. Got %x want %x", pubkey, pubs[i]) + } + } +} + func TestChainID(t *testing.T) { key, _ := crypto.GenerateKey() diff --git a/types/transaction.go b/types/transaction.go index 3788eb61..dfc1085c 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -45,8 +45,9 @@ type Transaction struct { gasAssetID uint64 gasPrice *big.Int // caches - hash atomic.Value - size atomic.Value + hash atomic.Value + extendHash atomic.Value + size atomic.Value } // NewTransaction initialize a transaction. @@ -65,8 +66,21 @@ func NewTransaction(assetID uint64, price *big.Int, actions ...*Action) *Transac // GasAssetID returns transaction gas asset id. func (tx *Transaction) GasAssetID() uint64 { return tx.gasAssetID } -// GasPrice returns transaction gas price. -func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.gasPrice) } +func (tx *Transaction) PayerExist() bool { + return tx.gasPrice.Cmp(big.NewInt(0)) == 0 && tx.actions[0].fp != nil +} + +// GasPrice returns transaction Higher gas price . +func (tx *Transaction) GasPrice() *big.Int { + gasPrice := new(big.Int) + if tx.gasPrice.Cmp(big.NewInt(0)) == 0 { + if price := tx.actions[0].PayerGasPrice(); price == nil { + return big.NewInt(0) + } + return gasPrice.Set(tx.actions[0].PayerGasPrice()) + } + return gasPrice.Set(tx.gasPrice) +} // Cost returns all actions gasprice * gaslimit. func (tx *Transaction) Cost() *big.Int { @@ -111,11 +125,26 @@ func (tx *Transaction) Hash() common.Hash { if hash := tx.hash.Load(); hash != nil { return hash.(common.Hash) } - v := RlpHash(tx) + var acts [][]interface{} + for _, a := range tx.actions { + acts = append(acts, a.IgnoreExtend()) + } + v := RlpHash([]interface{}{tx.gasAssetID, tx.gasPrice, acts}) tx.hash.Store(v) return v } +// ExtensHash hashes the RLP encoding of tx. +func (tx *Transaction) ExtensHash() common.Hash { + if hash := tx.extendHash.Load(); hash != nil { + return hash.(common.Hash) + } + + v := RlpHash(tx) + tx.extendHash.Store(v) + return v +} + // Size returns the true RLP encoded storage size of the transaction, func (tx *Transaction) Size() common.StorageSize { if size := tx.size.Load(); size != nil { @@ -128,7 +157,7 @@ func (tx *Transaction) Size() common.StorageSize { } // Check the validity of all fields -func (tx *Transaction) Check(conf *params.ChainConfig) error { +func (tx *Transaction) Check(fid uint64, conf *params.ChainConfig) error { if len(tx.actions) == 0 { return ErrEmptyActions } @@ -143,7 +172,7 @@ func (tx *Transaction) Check(conf *params.ChainConfig) error { } for _, action := range tx.actions { - if err := action.Check(conf); err != nil { + if err := action.Check(fid, conf); err != nil { return err } } @@ -182,6 +211,36 @@ func (tx *Transaction) NewRPCTransaction(blockHash common.Hash, blockNumber uint return result } +type RPCTransactionWithPayer struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber uint64 `json:"blockNumber"` + Hash common.Hash `json:"txHash"` + TransactionIndex uint64 `json:"transactionIndex"` + RPCActionsWithPayer []*RPCActionWithPayer `json:"actions"` + GasAssetID uint64 `json:"gasAssetID"` + GasPrice *big.Int `json:"gasPrice"` + GasCost *big.Int `json:"gasCost"` +} + +func (tx *Transaction) NewRPCTransactionWithPayer(blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransactionWithPayer { + result := new(RPCTransactionWithPayer) + if blockHash != (common.Hash{}) { + result.BlockHash = blockHash + result.BlockNumber = blockNumber + result.TransactionIndex = index + } + result.Hash = tx.Hash() + ras := make([]*RPCActionWithPayer, len(tx.GetActions())) + for index, action := range tx.GetActions() { + ras[index] = action.NewRPCActionWithPayer(uint64(index)) + } + result.RPCActionsWithPayer = ras + result.GasAssetID = tx.gasAssetID + result.GasPrice = tx.gasPrice + result.GasCost = tx.Cost() + return result +} + // TxByNonce sort by transaction first action nonce type TxByNonce []*Transaction